/* ============================================================
   app.jsx — shell, navigation, routing, tweaks
   ============================================================ */
const ACCENTS = {
  brand:      { h: 48,  c: 0.185, l: 0.68,  hex: '#f97316' },
  violet:     { h: 292, c: 0.155, l: 0.60,  hex: '#7c6df2' },
  terracotta: { h: 28,  c: 0.105, l: 0.585, hex: '#c2683f' },
  rose:       { h: 12,  c: 0.105, l: 0.585, hex: '#c05f5c' },
  amber:      { h: 70,  c: 0.105, l: 0.62,  hex: '#9a8420' },
  teal:       { h: 195, c: 0.085, l: 0.585, hex: '#3a8a93' },
  indigo:     { h: 268, c: 0.095, l: 0.56,  hex: '#6b62c7' },
};
const ACCENT_BY_HEX = Object.fromEntries(Object.entries(ACCENTS).map(([k, v]) => [v.hex, v]));

const APP_CONFIG = window.__APP_CONFIG || {};

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "accent": "#f97316",
  "dark": false,
  "density": "regular",
  "fontSize": 15,
  "skin": "warm",
  "amb": true
}/*EDITMODE-END*/;

const NAV = [
  { group: 'ภาพรวม' },
  { id: 'dashboard', label: 'แดชบอร์ด', icon: I.dash },
  { id: 'analytics', label: 'วิเคราะห์เชิงลึก', icon: I.pie },
  { id: 'alerts', label: 'การแจ้งเตือน', icon: I.bell, alert: true },
  { id: 'calendar', label: 'ปฏิทินสอบเทียบ', icon: I.calendar },
  { id: 'calplan', label: 'แผนสอบเทียบประจำปี', icon: I.shield },
  { id: 'templog', label: 'บันทึกอุณหภูมิ', icon: I.thermo },
  { id: 'repair', label: 'งานแจ้งซ่อม', icon: I.wrench },
  { id: 'reagents', label: 'น้ำยา & QC', icon: I.flask },
  { id: 'audit', label: 'ประวัติการแก้ไข', icon: I.history },
  { id: 'docreg', label: 'ดัชนีเอกสาร', icon: I.copy },
  { group: 'ทะเบียน' },
  { id: 'inventory', label: 'รายการเครื่องมือ', icon: I.list },
  { id: 'add', label: 'เพิ่มเครื่องมือ', icon: I.addbox },
];

const TITLES = {
  dashboard: ['แดชบอร์ด', 'ภาพรวมความพร้อมเครื่องมือทั้งห้องปฏิบัติการ'],
  analytics: ['วิเคราะห์เชิงลึก', 'การกระจาย แนวโน้มสอบเทียบ และสถิติเครื่องมือ'],
  alerts: ['การแจ้งเตือน', 'เครื่องมือที่ใกล้ครบกำหนดหรือเกินกำหนดสอบเทียบ'],
  calendar: ['ปฏิทินสอบเทียบ & PM', 'กำหนดการสอบเทียบและบำรุงรักษารายเดือน'],
  calplan: ['แผนสอบเทียบประจำปี', 'ทะเบียน/แผนสอบเทียบทั้งปี · เครื่องมือ × เดือน แยกตามแผนก'],
  templog: ['บันทึกอุณหภูมิ', 'แบบบันทึกอุณหภูมิตู้เย็น/ตู้แช่/ตู้อบ · พิมพ์ฟอร์ม FM-CL-00-045'],
  repair: ['งานแจ้งซ่อม', 'แจ้งซ่อม/ซ่อมบำรุงเครื่องมือ · เวิร์กโฟลว์ แจ้ง → กำลังซ่อม → เสร็จ'],
  reagents: ['น้ำยา & สารควบคุมคุณภาพ (QC)', 'ทะเบียนล็อตน้ำยา/วัสดุควบคุม · วันหมดอายุ · ผล QC · ผูกกับเครื่อง'],
  audit: ['ประวัติการแก้ไข (Audit trail)', 'บันทึกทุกการเปลี่ยนแปลง — ใคร แก้อะไร เมื่อไร'],
  docreg: ['ดัชนีเอกสาร (Document register)', 'ทะเบียนเอกสารควบคุม · ฉบับที่ · วันบังคับใช้ · รอบทบทวน'],
  inventory: ['รายการเครื่องมือ', 'ทะเบียนเครื่องมือทางห้องปฏิบัติการทั้งหมด'],
  add: ['เพิ่มเครื่องมือใหม่', 'ลงทะเบียนพร้อมออกรหัสอัตโนมัติ'],
  detail: ['รายละเอียดเครื่องมือ', 'บัตรประจำเครื่องและประวัติการสอบเทียบ'],
  users: ['จัดการผู้ใช้', 'เพิ่ม/แก้ไข/ระงับสิทธิ์ผู้ใช้งานระบบ'],
};

const ADMIN_PIN = '2569';
const THDAY = ['อาทิตย์', 'จันทร์', 'อังคาร', 'พุธ', 'พฤหัสบดี', 'ศุกร์', 'เสาร์'];

function TopClock() {
  const [now, setNow] = useState(new Date());
  useEffect(() => { const t = setInterval(() => setNow(new Date()), 1000); return () => clearInterval(t); }, []);
  const p = (n) => String(n).padStart(2, '0');
  return (
    <div className="topclock">
      <b>{p(now.getHours())}:{p(now.getMinutes())}:{p(now.getSeconds())}</b><br />
      <span>{THDAY[now.getDay()]} {now.getDate()} {THMONTH[now.getMonth()]} {now.getFullYear() + 543}</span>
    </div>
  );
}

const LS_KEY = 'labinstrument.state.v1';
const AUTH_KEY = 'labinstrument.auth.v1';
function loadState() {
  try { return JSON.parse(localStorage.getItem(LS_KEY)) || {}; } catch (e) { return {}; }
}
function loadAuth() {
  try { return JSON.parse(localStorage.getItem(AUTH_KEY)) || null; } catch (e) { return null; }
}

/* deep-link routing — a scanned QR opens #/detail/<code> */
function routeFromHash() {
  const h = (window.location.hash || '').replace(/^#/, '');
  const m = h.match(/^\/detail\/(.+)$/);
  if (m) return { view: 'detail', params: { code: decodeURIComponent(m[1]) } };
  return null;
}

/* Google Sheets returns date cells as full ISO datetime strings
   (e.g. "2026-01-15T00:00:00.000Z"); the components expect the same
   "YYYY-MM-DD" form that data.js uses. Normalise the whole payload
   so the data source is the only thing that changed. */
const ISO_DT = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/;
function normalizeGas(value) {
  if (typeof value === 'string') {
    return ISO_DT.test(value) ? value.slice(0, 10) : value;
  }
  if (Array.isArray(value)) return value.map(normalizeGas);
  if (value && typeof value === 'object') {
    const out = {};
    for (const k in value) out[k] = normalizeGas(value[k]);
    return out;
  }
  return value;
}

function App() {
  const [t, setTweak] = useTweaks({ ...TWEAK_DEFAULTS, ...(APP_CONFIG.tweakDefaults || {}) });
  const midnight = t.skin === 'midnight';
  const [auth, setAuth] = useState(loadAuth);
  const [locked, setLocked] = useState(false);
  const [admin, setAdmin] = useState(false);
  const [pinOpen, setPinOpen] = useState(false);
  const [pinVal, setPinVal] = useState('');
  const [pinErr, setPinErr] = useState('');
  const [deleted, setDeleted] = useState([]);
  const [menuOpen, setMenuOpen] = useState(false);
  const [route, setRoute] = useState(() => routeFromHash() || { view: 'dashboard', params: {} });
  const persisted = useMemo(loadState, []);
  const [added, setAdded] = useState(persisted.added || []);
  const [overrides, setOverrides] = useState(persisted.overrides || {});
  const [editCode, setEditCode] = useState(null);
  const [toast, setToast] = useState(null);
  const [railOpen, setRailOpen] = useState(false);
  const [collapsed, setCollapsed] = useState(false);
  const [deptOpen, setDeptOpen] = useState(true);

  // --- GAS data loading ---
  const [gasData, setGasData] = useState(null);
  const [loading, setLoading] = useState(window.Backend && window.Backend.mode === 'gas');
  const [loadErr, setLoadErr] = useState(false);
  const [gasUsers, setGasUsers] = useState([]);

  useEffect(() => {
    if (!window.Backend || window.Backend.mode !== 'gas') return;
    setLoading(true);
    window.Backend.loadAll().then(function (rawData) {
      var data = rawData ? normalizeGas(rawData) : rawData;
      if (data && data.ok !== false && data.instruments) {
        setGasData(data);
        setGasUsers(data.users || []);
        if (data.reagents && window.Reagents) {
          data.reagents.forEach(function (r) { window.Reagents.upsert(r); });
        }
        if (data.audit && window.Audit) {
          var existing = window.Audit.all();
          if (!existing.length) {
            data.audit.forEach(function (a) { window.Audit.all().push(a); });
          }
        }
      } else {
        setLoadErr(true);
      }
      setLoading(false);
    }).catch(function () {
      setLoadErr(true);
      setLoading(false);
    });
  }, []);

  // derive admin from auth role (GAS mode uses role from Users tab)
  useEffect(() => {
    if (auth && auth.role === 'admin') setAdmin(true);
  }, [auth]);

  // keep URL hash in sync with the detail route, and react to scans
  // that change the hash while the app is already open (back/forward too)
  useEffect(() => {
    const onHash = () => { const r = routeFromHash(); if (r) setRoute(r); };
    window.addEventListener('hashchange', onHash);
    return () => window.removeEventListener('hashchange', onHash);
  }, []);
  useEffect(() => {
    const desired = (route.view === 'detail' && route.params.code)
      ? '#/detail/' + encodeURIComponent(route.params.code) : '';
    if ((window.location.hash || '') !== desired) {
      try { history.replaceState(null, '', window.location.pathname + window.location.search + desired); } catch (e) {}
    }
  }, [route]);

  // persist edits + additions across reloads
  useEffect(() => {
    try { localStorage.setItem(LS_KEY, JSON.stringify({ added, overrides })); } catch (e) {}
  }, [added, overrides]);

  const instruments = useMemo(() => {
    var baseData = (gasData && gasData.instruments) ? gasData.instruments : window.LAB_DATA.instruments;
    const base = [...added, ...baseData].filter(i => !deleted.includes(i.code));
    return base.map(i => overrides[i.code] ? { ...i, ...overrides[i.code] } : i);
  }, [added, overrides, deleted, gasData]);

  const instrRef = useRef(instruments);
  instrRef.current = instruments;

  // identify current user to the audit store + seed demo data once
  useEffect(() => {
    window.__CURRENT_USER = auth ? (auth.user || 'นักเทคนิคการแพทย์') : 'นักเทคนิคการแพทย์';
    window.__CURRENT_ROLE = auth ? (auth.role || 'mt') : 'mt';
  }, [auth]);
  useEffect(() => {
    if (!gasData) {
      if (window.Reagents) window.Reagents.seedIfEmpty(window.seedReagents(window.LAB_DATA.instruments));
      if (window.Audit) window.Audit.seedIfEmpty(window.__seedAudit ? window.__seedAudit() : []);
    }
  }, [gasData]);

  const AUDIT_FIELDS = {
    type: 'ชนิดเครื่องมือ', brand: 'ยี่ห้อ', model: 'รุ่น/Model', serial: 'Serial No.', asset: 'รหัสครุภัณฑ์',
    cal: 'วันสอบเทียบ', exp: 'วันครบกำหนด', note: 'หมายเหตุ', location: 'ที่ตั้ง', owner: 'ผู้รับผิดชอบ',
    provider: 'หน่วยงานสอบเทียบ', certNo: 'เลขใบรับรอง', pmLast: 'PM ล่าสุด', pmNext: 'PM ครั้งถัดไป',
    vendor: 'ผู้แทนจำหน่าย', firmware: 'Firmware', warrantyEnd: 'วันหมดประกัน', serviceContract: 'สัญญาบำรุงรักษา',
    condition: 'สภาพเมื่อรับเข้า', commissionDate: 'วันเริ่มใช้งาน', lifecycle: 'สถานะทะเบียน',
  };
  const OP_LABEL = { up: 'ใช้งานได้', repair: 'รอซ่อมบำรุง', down: 'งดใช้ชั่วคราว' };

  const updateInstrument = useCallback((code, patch) => {
    const cur = (instrRef.current || []).find(i => i.code === code) || {};
    const meta = window.instrumentMeta ? window.instrumentMeta(cur) : {};
    const baseline = (k) => {
      if (k in cur && cur[k] != null && cur[k] !== '') return cur[k];
      if (k in meta && meta[k] != null) return meta[k];
      return cur[k];
    };
    if (window.Audit) {
      const curOp = cur.opKey || (meta.op ? meta.op.key : 'up');
      if ('opKey' in patch && patch.opKey !== curOp) {
        window.Audit.add({ action: 'inst_status', code, target: cur.type || code, field: 'สถานะการใช้งาน', from: OP_LABEL[curOp] || curOp, to: OP_LABEL[patch.opKey] || patch.opKey });
      }
      Object.keys(patch).forEach(k => {
        if (k === 'opKey' || !(k in AUDIT_FIELDS)) return;
        const before = baseline(k), after = patch[k];
        if ((before == null ? '' : String(before)) === (after == null ? '' : String(after))) return;
        const isDate = ['cal', 'exp', 'pmLast', 'pmNext', 'commissionDate', 'warrantyEnd'].includes(k);
        const fmt = (v) => isDate && v ? fmtDate(v) : (v || '—');
        window.Audit.add({ action: 'inst_edit', code, target: cur.type || code, field: AUDIT_FIELDS[k], from: fmt(before), to: fmt(after) });
      });
    }
    // optimistic update locally
    setOverrides(o => ({ ...o, [code]: { ...o[code], ...patch } }));
    // sync to GAS in background
    window.Backend.save({ code, ...patch });
    setToast('บันทึกการแก้ไขแล้ว · ' + code);
    setTimeout(() => setToast(null), 2600);
  }, []);
  const editInst = useMemo(() => editCode ? instruments.find(i => i.code === editCode) : null, [editCode, instruments]);

  // instrument counts per department (for the collapsible dept menu)
  const deptCounts = useMemo(() => {
    const m = {};
    instruments.forEach(i => { if (i.dept) m[i.dept] = (m[i.dept] || 0) + 1; });
    return Object.keys(DEPT_META).filter(d => m[d]).map(d => ({ dept: d, n: m[d] }));
  }, [instruments]);

  const deleteInstrument = useCallback((code) => {
    if (!admin) return;
    if (!window.confirm('ยืนยันลบเครื่องมือ ' + code + ' ?')) return;
    const cur = (instrRef.current || []).find(i => i.code === code) || {};
    setDeleted(d => [...d, code]);
    setAdded(a => a.filter(i => i.code !== code));
    window.Backend.remove(code);
    if (window.Audit) window.Audit.add({ action: 'inst_delete', code, target: cur.type || code, detail: 'ลบเครื่องมือออกจากทะเบียน' });
    setToast('ลบเครื่องมือแล้ว · ' + code);
    setTimeout(() => setToast(null), 2600);
    setRoute({ view: 'inventory', params: {} });
  }, [admin]);

  const tryAdmin = useCallback(() => {
    if (auth && auth.role === 'admin') {
      if (admin) { setAdmin(false); if (window.Audit) window.Audit.add({ action: 'admin_logout', detail: 'ออกจากโหมดผู้ดูแลระบบ' }); setToast('ออกจากโหมดผู้ดูแลแล้ว'); setTimeout(() => setToast(null), 2200); }
      else { setAdmin(true); if (window.Audit) window.Audit.add({ action: 'admin_login', detail: 'เข้าสู่โหมดผู้ดูแลระบบ' }); setToast('เข้าสู่โหมดผู้ดูแลระบบ'); setTimeout(() => setToast(null), 2200); }
      return;
    }
    if (admin) { setAdmin(false); if (window.Audit) window.Audit.add({ action: 'admin_logout', detail: 'ออกจากโหมดผู้ดูแลระบบ' }); setToast('ออกจากโหมดผู้ดูแลแล้ว'); setTimeout(() => setToast(null), 2200); }
    else { setPinVal(''); setPinErr(''); setPinOpen(true); }
  }, [admin, auth]);
  const submitPin = useCallback(() => {
    if (pinVal === ADMIN_PIN) { setAdmin(true); setPinOpen(false); if (window.Audit) window.Audit.add({ action: 'admin_login', detail: 'เข้าสู่โหมดผู้ดูแลระบบ' }); setToast('เข้าสู่โหมดผู้ดูแลระบบ'); setTimeout(() => setToast(null), 2200); }
    else setPinErr('PIN ไม่ถูกต้อง');
  }, [pinVal]);

  const alertCount = useMemo(() => instruments.filter(i => { const k = statusOf(i).key; return k === 'danger' || k === 'warn'; }).length, [instruments]);

  const go = useCallback((view, params = {}) => { setRoute({ view, params }); setRailOpen(false); window.scrollTo(0, 0); }, []);

  // apply tweaks to DOM
  useEffect(() => {
    const a = ACCENT_BY_HEX[t.accent] || ACCENTS.terracotta;
    const r = document.documentElement;
    let l = a.l, c = a.c;
    if (t.skin === 'midnight') {
      l = Math.min(0.84, a.l + 0.19); c = a.c + 0.05;
      r.setAttribute('data-theme', 'dark');
      r.setAttribute('data-skin', 'midnight');
    } else if (t.skin === 'saas') {
      r.setAttribute('data-theme', 'light');
      r.setAttribute('data-skin', 'saas');
    } else {
      r.setAttribute('data-theme', t.dark ? 'dark' : 'light');
      r.removeAttribute('data-skin');
    }
    r.style.setProperty('--accent-h', a.h);
    r.style.setProperty('--accent-c', c);
    r.style.setProperty('--accent-l', l);
    let fs = t.fontSize || 15;
    if (t.density === 'monitor') fs -= 1.5; else if (t.density === 'compact') fs -= 0.5;
    r.style.setProperty('--fs', fs + 'px');
    r.setAttribute('data-density', t.density || 'regular');
  }, [t.accent, t.dark, t.fontSize, t.skin, t.density]);

  const signIn = useCallback((info) => {
    const rec = { user: info.user, role: info.role, email: info.email || '', gasUser: info.gasUser || null, at: Date.now() };
    setAuth(rec);
    if (info.role === 'admin') setAdmin(true);
    if (info.remember) { try { localStorage.setItem(AUTH_KEY, JSON.stringify(rec)); } catch (e) {} }
  }, []);
  const signOut = useCallback(() => {
    setAuth(null); setLocked(false); setAdmin(false);
    try { localStorage.removeItem(AUTH_KEY); } catch (e) {}
  }, []);

  const [title, sub] = TITLES[route.view] || ['', ''];

  if (!auth) return <LoginScreen onEnter={signIn} />;

  // loading screen while fetching GAS data
  if (loading) return (
    <div style={{ position: 'fixed', inset: 0, display: 'grid', placeItems: 'center', background: 'var(--surface, #faf8f3)' }}>
      <div style={{ textAlign: 'center' }}>
        <div style={{ width: 48, height: 48, border: '4px solid var(--line, #e5e5e5)', borderTopColor: 'var(--accent, #f97316)', borderRadius: 99, animation: 'spin 1s linear infinite', margin: '0 auto 16px' }} />
        <div style={{ fontSize: 15, fontWeight: 600, color: 'var(--ink, #333)' }}>กำลังโหลดข้อมูลจาก Google Sheet…</div>
        <div style={{ fontSize: 13, color: 'var(--ink-3, #999)', marginTop: 6 }}>กรุณารอสักครู่</div>
      </div>
    </div>
  );

  // navigation items with admin-only users page
  const navItems = [...NAV];
  if (admin) {
    navItems.push({ group: 'ผู้ดูแลระบบ' });
    navItems.push({ id: 'users', label: 'จัดการผู้ใช้', icon: I.user, adminOnly: true });
  }

  let body;
  if (route.view === 'dashboard') body = <Dashboard instruments={instruments} go={go} setFilter={() => {}} />;
  else if (route.view === 'analytics') body = <Analytics instruments={instruments} go={go} />;
  else if (route.view === 'inventory') body = <Inventory instruments={instruments} go={go} initial={route.params} globalDensity={t.density} admin={admin} onDelete={deleteInstrument} />;
  else if (route.view === 'detail') body = <Detail instruments={instruments} code={route.params.code} go={go} onEdit={setEditCode} admin={admin} onDelete={deleteInstrument} />;
  else if (route.view === 'add') body = <InstrumentWizard instruments={instruments} go={go} onCreate={(i) => { setAdded(a => [i, ...a]); window.Backend.add(i); if (window.Audit) window.Audit.add({ action: 'inst_add', code: i.code, target: i.type || i.code, detail: 'ลงทะเบียนเครื่องมือใหม่' }); }} />;
  else if (route.view === 'calendar') body = <CalendarView instruments={instruments} go={go} />;
  else if (route.view === 'calplan') body = <CalPlan instruments={instruments} go={go} />;
  else if (route.view === 'templog') body = <TempLog instruments={instruments} />;
  else if (route.view === 'repair') body = <RepairLog instruments={instruments} onSetStatus={(code, op) => updateInstrument(code, { opKey: op })} go={go} />;
  else if (route.view === 'reagents') body = <ReagentsView instruments={instruments} go={go} initial={route.params} admin={admin} />;
  else if (route.view === 'audit') body = <AuditTrail instruments={instruments} go={go} admin={admin} />;
  else if (route.view === 'docreg') body = <DocRegister go={go} />;
  else if (route.view === 'alerts') body = <Alerts instruments={instruments} go={go} initial={route.params.stat} />;
  else if (route.view === 'users' && admin) body = <UserManage users={gasUsers} departments={gasData ? gasData.departments : []} onUsersChange={() => { window.Backend.loadAll().then(d => { var nd = d ? normalizeGas(d) : d; if (nd && nd.users) setGasUsers(nd.users); }); }} />;

  return (
    <>
    {t.amb && <><div className="amb" /><div className="amb-grain" /></>}
    <div className={`app${collapsed ? ' rail-collapsed' : ''}${railOpen ? ' rail-open' : ''}${t.amb ? ' has-amb' : ''}`}>
      {railOpen && <div className="scrim" style={{ zIndex: 18 }} onClick={() => setRailOpen(false)} />}
      {/* rail */}
      <aside className="rail">
        <div className="brand">
          <div className="brand-mark"><I.micro size={20} /></div>
          <div className="brand-text">
            <div className="brand-name">LabAsset</div>
            <div className="brand-sub">ระบบบริหารเครื่องมือห้องปฏิบัติการ</div>
          </div>
          <button className="rail-collapse" onClick={() => setCollapsed(c => !c)} title={collapsed ? 'ขยายเมนู' : 'ย่อเมนู'}>
            <I.chevR size={16} style={{ transform: collapsed ? 'none' : 'rotate(180deg)', transition: 'transform .2s' }} />
          </button>
        </div>
        <nav className="nav">
          {navItems.map((n, idx) => n.group ? (
            <div key={idx} className="nav-label">{n.group}</div>
          ) : n.id === 'inventory' ? (
            <div key={n.id}>
              <button className={`nav-item${(route.view === 'inventory' && !route.params.dept) || route.view === 'detail' ? ' active' : ''}`} onClick={() => go('inventory')}>
                <n.icon size={19} />
                <span className="txt">{n.label}</span>
                <span className="nav-caret txt" onClick={(e) => { e.stopPropagation(); setDeptOpen(o => !o); }} title={deptOpen ? 'ยุบแผนก' : 'แสดงแผนก'}>
                  <I.chevR size={14} style={{ transform: deptOpen ? 'rotate(90deg)' : 'none', transition: 'transform .2s' }} />
                </span>
              </button>
              {deptOpen && !collapsed && (
                <div className="nav-sub">
                  {deptCounts.map(d => {
                    const on = route.view === 'inventory' && route.params.dept === d.dept;
                    return (
                      <button key={d.dept} className={`nav-subitem${on ? ' active' : ''}`} onClick={() => go('inventory', { dept: d.dept })}>
                        <span className="dept-dot" style={{ background: deptColor(d.dept) }}></span>
                        <span className="txt">{(DEPT_META[d.dept] || {}).short || d.dept}</span>
                        <span className="nav-subcount tnum">{d.n}</span>
                      </button>
                    );
                  })}
                </div>
              )}
            </div>
          ) : (
            <button key={n.id} className={`nav-item${route.view === n.id ? ' active' : ''}`} onClick={() => go(n.id)}>
              <n.icon size={19} />
              <span className="txt">{n.label}</span>
              {n.alert && alertCount > 0 && <span className="badge-count tnum">{alertCount}</span>}
            </button>
          ))}
        </nav>
      </aside>

      {/* main */}
      <div className="main">
        <header className="topbar">
          <button className="btn btn-icon btn-ghost rail-toggle" onClick={() => setRailOpen(o => !o)}><I.menu size={20} /></button>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div className="page-title">{title}</div>
            <div className="page-sub">{sub}</div>
          </div>
          {midnight && (
            <div className="live-badge"><span className="live-dot"></span>LIVE</div>
          )}
          <TopClock />
          <div className="status-online"><span className="live-dot"></span> {window.Backend.mode === 'gas' ? 'ONLINE · GAS' : 'LOCAL'}</div>
          <button className="btn btn-icon topbell" title="การแจ้งเตือน" onClick={() => go('alerts')}>
            <I.bell size={18} />
            {alertCount > 0 && <span className="topbell-dot tnum">{alertCount > 99 ? '99+' : alertCount}</span>}
          </button>
          <button className="btn btn-icon" title={admin ? 'โหมดผู้ดูแล (คลิกเพื่อออก)' : 'เข้าสู่โหมดผู้ดูแล'} onClick={tryAdmin}
            style={admin ? { color: 'var(--ok)', borderColor: 'var(--ok)', background: 'var(--ok-soft)' } : {}}>
            {admin ? <I.unlock size={18} /> : <I.lock size={18} />}
          </button>
          <button className="btn btn-icon" title={t.dark ? 'โหมดสว่าง' : 'โหมดมืด'} onClick={() => setTweak(midnight ? 'skin' : 'dark', midnight ? 'warm' : !t.dark)}>
            {(t.dark || midnight) ? <I.sun size={18} /> : <I.moon size={18} />}
          </button>
          <div className="avatar-wrap">
            <button className="avatar-btn" onClick={() => setMenuOpen(o => !o)} title="บัญชีผู้ใช้">{(auth.user || 'MT').slice(0, 2).toUpperCase()}</button>
            {menuOpen && (
              <>
                <div style={{ position: 'fixed', inset: 0, zIndex: 40 }} onClick={() => setMenuOpen(false)} />
                <div className="avatar-menu card">
                  <div className="am-head">
                    <div style={{ width: 34, height: 34, borderRadius: 99, background: 'var(--accent-soft)', color: 'var(--accent-strong)', display: 'grid', placeItems: 'center', fontWeight: 700, fontSize: 13, flex: 'none' }}>{(auth.user || 'MT').slice(0, 2).toUpperCase()}</div>
                    <div style={{ minWidth: 0 }}>
                      <div style={{ fontSize: 13, fontWeight: 600, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{auth.user || 'นักเทคนิคการแพทย์'}</div>
                      <div style={{ fontSize: 11, color: 'var(--ink-3)' }}>{({ mt: 'นักเทคนิคการแพทย์', chief: 'หัวหน้าห้องแล็บ', admin: 'ผู้ดูแลระบบ', user: 'ผู้ใช้ทั่วไป' })[auth.role] || 'ผู้ดูแลเครื่องมือ'}{admin ? ' · Admin' : ''}</div>
                    </div>
                  </div>
                  {auth.email && <div style={{ fontSize: 11, color: 'var(--ink-4)', padding: '0 14px 8px', fontFamily: 'var(--mono)' }}>{auth.email}</div>}
                  <button className="menu-item" onClick={() => { setMenuOpen(false); setLocked(true); }}><I.lock size={16} /> ล็อกหน้าจอ</button>
                  <button className="menu-item" onClick={() => { setMenuOpen(false); signOut(); }}><I.logout size={16} /> ออกจากระบบ</button>
                </div>
              </>
            )}
          </div>
        </header>
        <main className="content" key={route.view + (route.params.code || '')}>{body}</main>
      </div>

      {editInst && <EditDrawer inst={editInst} onClose={() => setEditCode(null)} onSave={updateInstrument} />}
      {locked && <LockScreen userName={auth.user} onUnlock={() => setLocked(false)} onLogout={signOut} />}
      {toast && (
        <div className="toast enter"><I.check size={17} /> {toast}</div>
      )}

      {/* Tweaks */}
      <TweaksPanel>
        <TweakSection label="สไตล์ (Skin)" />
        <TweakRadio label="แนวทาง" value={t.skin}
          options={[{ value: 'warm', label: 'Warm' }, { value: 'saas', label: 'SaaS' }, { value: 'midnight', label: 'Midnight' }]}
          onChange={v => setTweak(v === 'saas' ? { skin: 'saas', accent: '#7c6df2' } : v === 'warm' ? { skin: 'warm', accent: '#f97316' } : { skin: v })} />
        <TweakSection label="ธีม" />
        <TweakColor label={midnight ? 'สีนีออน (Accent)' : 'สีหลัก (Accent)'} value={t.accent}
          options={Object.values(ACCENTS).map(a => a.hex)}
          onChange={v => setTweak('accent', v)} />
        {!midnight && <TweakToggle label="โหมดมืด" value={t.dark} onChange={v => setTweak('dark', v)} />}
        <TweakSection label="การแสดงผล" />
        <TweakRadio label="ความหนาแน่น" value={t.density}
          options={[{ value: 'regular', label: 'ปกติ' }, { value: 'compact', label: 'แน่น' }, { value: 'monitor', label: 'จอแล็บ' }]}
          onChange={v => setTweak('density', v)} />
        <TweakSlider label="ขนาดตัวอักษร" value={t.fontSize} min={13} max={18} step={1} unit="px" onChange={v => setTweak('fontSize', v)} />
        <TweakToggle label="พื้นหลัง Ambient" value={t.amb} onChange={v => setTweak('amb', v)} />
      </TweaksPanel>
    </div>

    {pinOpen && (
      <>
        <div className="scrim" onClick={() => setPinOpen(false)} />
        <div className="modal">
          <div className="modal-ic"><I.lock size={26} /></div>
          <h3 style={{ margin: 0, fontSize: 19, fontWeight: 600 }}>เข้าสู่โหมดผู้ดูแลระบบ</h3>
          <p style={{ fontSize: 13, color: 'var(--ink-3)', margin: '6px 0 4px' }}>ใส่ PIN เพื่อปลดล็อกการแก้ไข/ลบข้อมูล<br/><span style={{ fontSize: 11.5, color: 'var(--ink-4)' }}>โหมดสาธิต · PIN: 2569</span></p>
          <input className="pin-input" type="password" maxLength={6} value={pinVal} autoFocus
            onChange={e => { setPinVal(e.target.value); setPinErr(''); }}
            onKeyDown={e => { if (e.key === 'Enter') submitPin(); }} placeholder="••••" />
          <div style={{ color: 'var(--danger)', fontSize: 12, fontFamily: 'var(--mono)', minHeight: 18, marginTop: 8 }}>{pinErr}</div>
          <div style={{ display: 'flex', gap: 10, marginTop: 8 }}>
            <button className="btn" style={{ flex: 1 }} onClick={() => setPinOpen(false)}>ยกเลิก</button>
            <button className="btn btn-primary" style={{ flex: 1 }} onClick={submitPin}><I.unlock size={16} /> เข้าสู่ระบบ</button>
          </div>
        </div>
      </>
    )}
    </>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
