// portal-app.jsx — root App with auth gate, header, sidebar, router, role-based access

const NAV_ITEMS = [
  { path: 'dashboard',  label: 'Dashboard',     icon: '◇', group: 'main',  requires: 'view' },
  { path: 'members',    label: 'Members',       icon: '☰', group: 'main',  requires: 'view' },
  { path: 'enroll',     label: 'New member',    icon: '✚', group: 'main',  requires: 'enroll' },
  { path: 'redeem',     label: 'Redeem',        icon: '✓', group: 'ops',   requires: 'redeem' },
  { path: 'fnb',        label: 'F&B discount',  icon: '%', group: 'ops',   requires: 'redeem' },
  { path: 'wellness',   label: 'Facilities log',icon: '~', group: 'ops',   requires: 'redeem' },
  { path: 'reports',    label: 'Reports',       icon: '▤', group: 'admin', requires: 'reports' },
  { path: 'audit',      label: 'Audit log',     icon: '⏱', group: 'admin', requires: 'audit' },
  { path: 'staff',      label: 'Staff',         icon: '◉', group: 'admin', requires: 'manage-staff' },
  { path: 'settings',   label: 'Settings',      icon: '⚙', group: 'admin', requires: 'manage-roles' },
];

const GROUPS = [
  { id: 'main',  label: '' },
  { id: 'ops',   label: 'Operations' },
  { id: 'admin', label: 'Admin' },
];

function Sidebar({ currentPath }) {
  const user = useStore(s => s.users.find(u => u.id === s.currentUserId));
  const role = user && user.role;
  return (
    <aside style={{
      width: 232,
      background: 'linear-gradient(180deg, #0b1626, #0a1424)',
      borderRight: '1px solid ' + T.borderSoft,
      display: 'flex',
      flexDirection: 'column',
      flexShrink: 0,
    }}>
      <div style={{ padding: '20px 18px 16px', display: 'flex', alignItems: 'center', gap: 10 }}>
        <Mark size={32} />
        <div className="pt-col" style={{ lineHeight: 1.05 }}>
          <div className="pt-display" style={{ fontSize: 19, fontWeight: 600, color: T.text, letterSpacing: '-0.005em' }}>Nepalirika</div>
          <div style={{ fontSize: 10, color: T.textMute, letterSpacing: '0.16em', textTransform: 'uppercase' }}>Membership · CRM</div>
        </div>
      </div>
      <hr className="pt-divider" />

      <nav style={{ padding: '14px 10px', flex: 1, overflowY: 'auto' }}>
        {GROUPS.map(g => {
          const items = NAV_ITEMS.filter(i => i.group === g.id && Store.can(i.requires, role));
          if (items.length === 0) return null;
          return (
            <div key={g.id} style={{ marginBottom: 14 }}>
              {g.label && <div style={{ fontSize: 10, color: T.textFaint, letterSpacing: '0.14em', textTransform: 'uppercase', padding: '6px 12px', fontWeight: 600 }}>{g.label}</div>}
              {items.map(it => {
                const active = currentPath === it.path;
                return (
                  <a key={it.path} href={'#/' + it.path} style={{
                    display: 'flex', alignItems: 'center', gap: 12,
                    padding: '9px 12px', margin: '1px 0',
                    borderRadius: 8,
                    textDecoration: 'none',
                    color: active ? T.bg : T.text,
                    background: active ? `linear-gradient(180deg, ${T.goldBright}, ${T.gold})` : 'transparent',
                    fontWeight: active ? 600 : 500,
                    fontSize: 14,
                    transition: 'background .15s, color .15s',
                  }}
                    onMouseEnter={e => { if (!active) e.currentTarget.style.background = 'rgba(255,255,255,0.05)'; }}
                    onMouseLeave={e => { if (!active) e.currentTarget.style.background = 'transparent'; }}
                  >
                    <span style={{ fontSize: 14, opacity: active ? 1 : 0.55, width: 16, textAlign: 'center' }}>{it.icon}</span>
                    {it.label}
                  </a>
                );
              })}
            </div>
          );
        })}
      </nav>

      <SessionFooter />
    </aside>
  );
}

function SessionFooter() {
  const user = useStore(s => s.users.find(u => u.id === s.currentUserId));
  const session = useStore(s => s.session);
  const [open, setOpen] = React.useState(false);
  if (!user) return null;
  const roleAccent = user.role === 'Super Admin' ? T.gold : user.role === 'Manager' ? T.silver : T.textMute;
  return (
    <>
      <div style={{ padding: '12px 14px', borderTop: '1px solid ' + T.borderSoft, cursor: 'pointer' }} onClick={() => setOpen(true)}>
        <div className="pt-row" style={{ gap: 10 }}>
          <div style={{
            width: 36, height: 36, borderRadius: '50%',
            background: user.role === 'Super Admin' ? 'linear-gradient(135deg,#3d2c0e,#1a1207)'
              : user.role === 'Manager' ? 'linear-gradient(135deg,#1c2a45,#0e1a2f)'
              : 'linear-gradient(135deg,#1a2334,#0b1322)',
            border: '1.5px solid ' + roleAccent,
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            color: roleAccent, fontWeight: 600, fontSize: 13, flexShrink: 0,
          }}>{user.initials}</div>
          <div className="pt-col" style={{ flex: 1, lineHeight: 1.2, minWidth: 0 }}>
            <div style={{ fontSize: 13, fontWeight: 600, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{user.name}</div>
            <div className="pt-faint" style={{ fontSize: 11 }}>
              <span style={{ color: roleAccent, fontWeight: 600 }}>{user.role}</span> · {session.property}
            </div>
          </div>
          <span className="pt-mute" style={{ fontSize: 14 }}>⌄</span>
        </div>
      </div>
      <SessionModal open={open} onClose={() => setOpen(false)} user={user} />
    </>
  );
}

function SessionModal({ open, onClose, user }) {
  const session = useStore(s => s.session);
  const toast = useToast();
  const confirmDlg = useConfirm();
  const [property, setProperty] = React.useState(session.property);
  const [pwOpen, setPwOpen] = React.useState(false);
  React.useEffect(() => { setProperty(session.property); }, [session.property, open]);

  const save = () => {
    Store.setSession({ property });
    toast.show('Property switched · ' + property);
    onClose();
  };

  return (
    <Modal open={open} title="Your session" onClose={onClose} footer={
      <>
        <Button variant="danger" onClick={() => {
          Store.logout();
          onClose();
        }}>↩ Sign out</Button>
        <div style={{ flex: 1 }} />
        <Button variant="ghost" onClick={onClose}>Cancel</Button>
        <Button variant="gold" onClick={save}>Save</Button>
      </>
    }>
      <div className="pt-row" style={{ gap: 14, marginBottom: 18, padding: 14, background: 'rgba(255,255,255,0.03)', borderRadius: 10 }}>
        <div style={{
          width: 56, height: 56, borderRadius: '50%',
          background: user.role === 'Super Admin' ? 'linear-gradient(135deg,#3d2c0e,#1a1207)' : 'linear-gradient(135deg,#1c2a45,#0e1a2f)',
          border: '1.5px solid ' + (user.role === 'Super Admin' ? T.gold : T.silver),
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          color: user.role === 'Super Admin' ? T.goldBright : '#cbd5e1',
          fontWeight: 600, fontSize: 20,
        }}>{user.initials}</div>
        <div className="pt-col" style={{ flex: 1, lineHeight: 1.3 }}>
          <div style={{ fontWeight: 600, fontSize: 16 }}>{user.name}</div>
          <div className="pt-faint" style={{ fontSize: 12 }}>{user.email}</div>
          <div className="pt-row" style={{ gap: 6, marginTop: 6 }}>
            <Badge variant={user.role === 'Super Admin' ? 'gold' : 'silver'}>{user.role}</Badge>
            <Badge>{user.bio}</Badge>
          </div>
        </div>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: '1fr', gap: 12 }}>
        <Select label="Working at · property" value={property} onChange={setProperty}
          options={['Biratchowk', 'Damak']} />
      </div>

      <div style={{ marginTop: 14, padding: 12, background: 'rgba(255,255,255,0.03)', borderRadius: 10 }}>
        <div className="pt-row" style={{ justifyContent: 'space-between', alignItems: 'center' }}>
          <div className="pt-col" style={{ lineHeight: 1.3 }}>
            <div style={{ fontWeight: 600, fontSize: 13 }}>Account security</div>
            <div className="pt-faint" style={{ fontSize: 11 }}>Change your password · enable 2FA (coming soon)</div>
          </div>
          <Button size="sm" onClick={() => setPwOpen(true)}>Change password</Button>
        </div>
      </div>

      {Store.can('manage') && (
        <div style={{ marginTop: 14, padding: 12, background: 'rgba(239,68,68,0.06)', border: '1px solid rgba(239,68,68,0.2)', borderRadius: 10 }}>
          <div className="pt-eyebrow" style={{ color: '#fca5a5', marginBottom: 6 }}>Super-admin · danger zone</div>
          <div className="pt-mute" style={{ fontSize: 12, marginBottom: 10 }}>
            Wipe all members, redemptions, audit history, and re-seed staff accounts. Everyone will be signed out.
          </div>
          <Button variant="danger" size="sm" onClick={async () => {
            const ok = await confirmDlg('This will wipe all members, redemptions, and audit history. Everyone will be signed out.', { title: 'Reset everything?', danger: true, confirmLabel: 'Reset' });
            if (ok) { Store.resetAll(); toast.show('Reset to seed data'); onClose(); }
          }}>Reset all data</Button>
        </div>
      )}

      <div className="pt-faint" style={{ fontSize: 11, marginTop: 14 }}>
        Permissions: {(window.ROLE_PERMS[user.role] || []).join(' · ')}
      </div>

      <ChangePasswordModal open={pwOpen} onClose={() => setPwOpen(false)} />
    </Modal>
  );
}

// SyncErrorBanner — shows a warning at the top of the app when local changes
// failed to save to the server. Prevents the user from thinking edits stuck
// when they didn't, and gives them a one-click "retry" (hydrate from server).
function SyncErrorBanner() {
  const errors = useStore(s => s.syncErrors || []);
  const [show, setShow] = React.useState(true);
  if (!errors.length || !show) return null;
  const lastErr = errors[0];
  return (
    <div style={{
      background: 'rgba(239,68,68,0.10)', borderBottom: '1px solid rgba(239,68,68,0.3)',
      padding: '10px 16px', color: '#fca5a5', fontSize: 13,
      display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap',
    }}>
      <span>⚠ {errors.length} change{errors.length === 1 ? '' : 's'} didn't save to the server.</span>
      <span className="pt-faint" style={{ fontSize: 11 }}>Last error: {lastErr.label} · {lastErr.error}</span>
      <div style={{ flex: 1 }} />
      <Button size="sm" onClick={() => { if (window.Store && window.Store.hydrate) window.Store.hydrate(); Store.clearSyncErrors(); setShow(false); }}>
        Reload from server
      </Button>
      <Button size="sm" variant="ghost" onClick={() => { Store.clearSyncErrors(); setShow(false); }}>Dismiss</Button>
    </div>
  );
}

function ChangePasswordModal({ open, onClose }) {
  const toast = useToast();
  const [cur, setCur] = React.useState('');
  const [next, setNext] = React.useState('');
  const [confirm, setConfirm] = React.useState('');
  const [error, setError] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const [done, setDone] = React.useState(false);
  React.useEffect(() => { if (open) { setCur(''); setNext(''); setConfirm(''); setError(''); setDone(false); setBusy(false); } }, [open]);

  async function save() {
    setError('');
    if (next !== confirm) return setError('New passwords do not match');
    if (next.length < 8) return setError('New password must be at least 8 characters');
    if (cur === next) return setError('New password must differ from current');
    setBusy(true);
    try {
      const res = await Store.changeOwnPassword(cur, next);
      if (!res || !res.ok) { setError((res && res.error) || 'Could not change password'); setBusy(false); return; }
      // SUCCESS: force a re-login so the new password is actually USED to get a
      // fresh JWT. This guarantees "what you typed is what works next time."
      setDone(true);
      setBusy(false);
      toast.show('Password changed — please sign in again with the new password.');
      setTimeout(() => { Store.logout(); }, 1500);
    } catch (err) {
      setError(err.message || 'Could not change password');
      setBusy(false);
    }
  }
  return (
    <Modal open={open} title="Change your password" onClose={done ? null : onClose} width={420} footer={
      done ? (
        <div className="pt-mute" style={{ fontSize: 12, padding: '6px 4px' }}>
          Signing you out in a moment…
        </div>
      ) : (
        <>
          <Button variant="ghost" onClick={onClose} disabled={busy}>Cancel</Button>
          <Button variant="gold" onClick={save} disabled={busy || !cur || !next || !confirm}>
            {busy ? 'Updating…' : 'Update password'}
          </Button>
        </>
      )
    }>
      {done ? (
        <div style={{ padding: 14, background: 'rgba(74,222,128,0.08)', border: '1px solid rgba(74,222,128,0.3)', borderRadius: 8, color: T.good, fontSize: 13 }}>
          ✓ Password updated on the server. You'll be signed out — log back in with your new password to confirm it works.
        </div>
      ) : (
        <>
          <div className="pt-col" style={{ gap: 12 }}>
            <Input label="Current password" value={cur} onChange={setCur} type="password" />
            <Input label="New password" value={next} onChange={setNext} type="password" hint="At least 8 characters · mix letters + numbers" />
            <Input label="Confirm new password" value={confirm} onChange={setConfirm} type="password" />
          </div>
          {error && (
            <div style={{ padding: '10px 12px', marginTop: 12, background: 'rgba(239,68,68,0.08)', border: '1px solid rgba(239,68,68,0.3)', borderRadius: 8, color: '#fca5a5', fontSize: 13 }}>⚠ {error}</div>
          )}
          <div className="pt-faint" style={{ fontSize: 11, marginTop: 14, lineHeight: 1.6 }}>
            On success you'll be signed out automatically — sign back in with the new password to confirm it stuck.<br />
            Forgot your current password? Ask your Super Admin to reset it from the Staff page.
          </div>
        </>
      )}
    </Modal>
  );
}

function Topbar({ onHamburger }) {
  const session = useStore(s => s.session);
  const members = useStore(s => s.members);
  const memberCount = members.filter(m => !m.deleted).length;
  const [q, setQ] = React.useState('');
  const [showResults, setShowResults] = React.useState(false);
  const wrapRef = React.useRef(null);

  // Live filter
  const ql = q.trim().toLowerCase();
  const matches = ql ? members.filter(m => !m.deleted && (
    (m.name || '').toLowerCase().includes(ql) ||
    (m.id || '').toLowerCase().includes(ql) ||
    (m.cardNumber || '').toLowerCase().includes(ql) ||
    (m.phone || '').toLowerCase().includes(ql) ||
    (m.email || '').toLowerCase().includes(ql)
  )).slice(0, 8) : [];

  React.useEffect(() => {
    function onClickOut(e) {
      if (wrapRef.current && !wrapRef.current.contains(e.target)) setShowResults(false);
    }
    document.addEventListener('mousedown', onClickOut);
    return () => document.removeEventListener('mousedown', onClickOut);
  }, []);

  function pick(m) {
    setQ(''); setShowResults(false);
    navigate('members/' + m.id);
  }

  return (
    <header style={{
      height: 56,
      borderBottom: '1px solid ' + T.borderSoft,
      background: 'rgba(11, 22, 38, 0.85)',
      backdropFilter: 'blur(12px)',
      display: 'flex',
      alignItems: 'center',
      padding: '0 14px',
      gap: 10,
      position: 'sticky',
      top: 0,
      zIndex: 50,
    }}>
      {/* Hamburger — mobile only */}
      <button onClick={onHamburger} aria-label="Open menu"
        className="pt-hamburger"
        style={{
          display: 'none', background: 'transparent', border: '1px solid ' + T.borderSoft,
          color: T.text, padding: '6px 10px', borderRadius: 8, fontSize: 18, cursor: 'pointer',
        }}>☰</button>

      <div ref={wrapRef} style={{ flex: 1, position: 'relative', maxWidth: 460 }}>
        <input
          className="pt-input"
          placeholder="Search members · name, phone, ID, card #"
          value={q}
          onChange={e => { setQ(e.target.value); setShowResults(true); }}
          onFocus={() => setShowResults(true)}
          style={{ paddingLeft: 32, height: 36 }}
        />
        <span style={{ position: 'absolute', left: 10, top: '50%', transform: 'translateY(-50%)', color: T.textFaint }}>⌕</span>
        {showResults && q && (
          <div style={{
            position: 'absolute', top: '100%', left: 0, right: 0, marginTop: 4,
            background: '#0e1a2f', border: '1px solid ' + T.borderSoft, borderRadius: 8,
            boxShadow: '0 8px 24px rgba(0,0,0,0.4)', zIndex: 60, maxHeight: 340, overflowY: 'auto',
          }}>
            {matches.length === 0 ? (
              <div className="pt-mute" style={{ padding: 12, fontSize: 13 }}>No members match "{q}"</div>
            ) : matches.map(m => (
              <div key={m.id} onClick={() => pick(m)} style={{
                padding: '8px 12px', cursor: 'pointer', borderBottom: '1px solid ' + T.borderSoft,
                display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8,
              }}
                onMouseEnter={e => e.currentTarget.style.background = 'rgba(200,162,90,0.06)'}
                onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
                <div className="pt-col" style={{ minWidth: 0 }}>
                  <div style={{ fontWeight: 600, fontSize: 13, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{m.name}</div>
                  <div className="pt-faint" style={{ fontSize: 11 }}>{m.id} · {m.phone || m.email || '—'}</div>
                </div>
                <Badge variant={m.type === 'wellness' ? 'gold' : 'silver'}>{m.type === 'wellness' ? 'Wellness' : 'Silver'}</Badge>
              </div>
            ))}
          </div>
        )}
      </div>
      <div style={{ flex: 1 }} />

      {/* Property selector (replaces fixed badge) */}
      <select
        className="pt-input pt-hide-mobile"
        value={session.property}
        onChange={e => Store.setSession({ property: e.target.value })}
        style={{ height: 32, padding: '0 8px', fontSize: 12 }}>
        <option value="Biratchowk">◆ Biratchowk</option>
        <option value="Damak">◆ Damak</option>
      </select>

      <Badge variant="gold" className="pt-hide-mobile">{memberCount} members</Badge>
      {Store.can('enroll') && <Button size="sm" onClick={() => navigate('enroll')}>+ New</Button>}
    </header>
  );
}

// ─── Route guard ─────────────────────────────────────────────
function NoAccess({ requires }) {
  return (
    <div>
      <PageHeader title="Access denied" eyebrow="Permission required" sub={`Your role doesn't have access to this area (needs · ${requires}).`} />
      <Card flat>
        <div className="pt-mute" style={{ fontSize: 14 }}>
          Ask a manager or super-admin to grant access, or pick another section from the sidebar.
        </div>
        <div className="pt-row" style={{ gap: 8, marginTop: 14 }}>
          <Button onClick={() => navigate('dashboard')}>← Go to dashboard</Button>
        </div>
      </Card>
    </div>
  );
}

// ─── Router ──────────────────────────────────────────────────
function Router() {
  const route = useRoute();
  const { path, params } = route;

  // Scroll content to top on route change
  React.useEffect(() => {
    const el = document.getElementById('pt-content');
    if (el) el.scrollTo({ top: 0 });
  }, [route.hash]);

  // Permission check per route
  const need = (NAV_ITEMS.find(i => i.path === path) || {}).requires || 'view';
  if (need && !Store.can(need)) {
    return <NoAccess requires={need} />;
  }

  if (path === 'dashboard') return <DashboardScreen />;
  if (path === 'members') {
    if (params[0]) return <ProfileScreen memberId={params[0]} />;
    return <MembersScreen />;
  }
  if (path === 'enroll') return <EnrollScreen />;
  if (path === 'redeem') return <RedeemScreen />;
  if (path === 'fnb') return <FnBScreen />;
  if (path === 'wellness') return <WellnessScreen />;
  if (path === 'reports') return <ReportsScreen />;
  if (path === 'audit') return <AuditScreen />;
  if (path === 'staff') return <StaffScreen />;
  if (path === 'settings') return <SettingsScreen />;
  return <DashboardScreen />;
}

// ─── Staff management (Super Admin only) ─────────────────────
function StaffScreen() {
  const users = useStore(s => s.users);
  const currentUserId = useStore(s => s.currentUserId);
  const audit = useStore(s => s.audit);
  const toast = useToast();
  const confirmDlg = useConfirm();
  const [editing, setEditing] = React.useState(null);   // user being edited (or {} for new)
  const [creds, setCreds] = React.useState(null);       // {user, password} after add or reset

  return (
    <div>
      <PageHeader title="Staff" eyebrow="Access · roles"
        sub={`${users.length} accounts · only Super Admins can add, edit roles or reset passwords.`}>
        <Button variant="gold" onClick={() => setEditing({})}>+ Add staff</Button>
      </PageHeader>

      <Card flat style={{ marginBottom: 14 }}>
        <h3 className="pt-h3" style={{ marginBottom: 12 }}>Role capabilities</h3>
        <table className="pt-table">
          <thead><tr><th>Role</th><th>What they can do</th></tr></thead>
          <tbody>
            <tr>
              <td><Badge variant="gold">Super Admin</Badge></td>
              <td className="pt-mute" style={{ fontSize: 13 }}>
                Everything. Add/remove/reset other staff. Delete members. Override F&B discount %. Wipe data.
              </td>
            </tr>
            <tr>
              <td><Badge variant="silver">Manager</Badge></td>
              <td className="pt-mute" style={{ fontSize: 13 }}>
                Assign rooms (comp + paid), check reports & audit log, enroll & edit members, log facilities, apply F&B discount at the standard %. Cannot delete members or audit entries, cannot exceed the discount cap.
              </td>
            </tr>
            <tr>
              <td><Badge>Associate</Badge></td>
              <td className="pt-mute" style={{ fontSize: 13 }}>
                Enroll new members, redeem benefits (pool / gym / hall / spa / cake), apply F&B discount, view dashboard. Cannot see Staff, Reports, or Audit log. Cannot edit or delete members.
              </td>
            </tr>
          </tbody>
        </table>
      </Card>

      <Card flat>
        <div className="pt-row" style={{ justifyContent: 'space-between', marginBottom: 12 }}>
          <h3 className="pt-h3">Accounts</h3>
          <span className="pt-faint" style={{ fontSize: 12 }}>Click a row to edit · reset password · change role</span>
        </div>
        <table className="pt-table">
          <thead><tr><th>Staff</th><th>Email · login id</th><th>Role</th><th>Property</th><th>Last activity</th><th></th></tr></thead>
          <tbody>
            {users.map(u => {
              const last = audit.find(a => a.who === u.name);
              const isMe = u.id === currentUserId;
              return (
                <tr key={u.id} className="pt-row-link" onClick={() => setEditing(u)}>
                  <td>
                    <div className="pt-row" style={{ gap: 10 }}>
                      <div style={{
                        width: 32, height: 32, borderRadius: '50%',
                        background: u.role === 'Super Admin' ? 'linear-gradient(135deg,#3d2c0e,#1a1207)' : 'linear-gradient(135deg,#1c2a45,#0e1a2f)',
                        border: '1.5px solid ' + (u.role === 'Super Admin' ? T.gold : T.silver),
                        display: 'flex', alignItems: 'center', justifyContent: 'center',
                        color: u.role === 'Super Admin' ? T.goldBright : '#cbd5e1',
                        fontWeight: 600, fontSize: 12,
                      }}>{u.initials}</div>
                      <div className="pt-col" style={{ lineHeight: 1.2 }}>
                        <div style={{ fontWeight: 600 }}>{u.name} {isMe && <Badge variant="gold" style={{ marginLeft: 6 }}>you</Badge>}</div>
                        <div className="pt-faint" style={{ fontSize: 11 }}>{u.bio}</div>
                      </div>
                    </div>
                  </td>
                  <td className="pt-mute pt-num">{u.email}</td>
                  <td><Badge variant={u.role === 'Super Admin' ? 'gold' : u.role === 'Manager' ? 'silver' : undefined}>{u.role}</Badge></td>
                  <td className="pt-mute">{u.property}</td>
                  <td className="pt-mute">{last ? fmtDateTime(last.ts) : <span className="pt-faint">—</span>}</td>
                  <td style={{ textAlign: 'right' }} onClick={e => e.stopPropagation()}>
                    <div className="pt-row" style={{ gap: 6, justifyContent: 'flex-end' }}>
                      <Button size="sm" onClick={() => setEditing(u)}>Edit</Button>
                      <Button size="sm" onClick={async () => {
                        const ok = await confirmDlg(`Reset password for ${u.name}? They'll need to sign in again.`, { title: 'Reset password', confirmLabel: 'Reset' });
                        if (ok) {
                          const res = await Promise.resolve(Store.resetUserPassword(u.id));
                          if (res.ok) { setCreds({ user: u, password: res.password, mode: 'reset' }); }
                          else { toast.show(res.error, { bad: true }); }
                        }
                      }}>Reset password</Button>
                      {!isMe && (
                        <Button size="sm" variant="danger" onClick={async () => {
                          const ok = await confirmDlg(`Remove ${u.name} from staff? This cannot be undone.`, { title: 'Remove staff member', danger: true, confirmLabel: 'Remove' });
                          if (ok) {
                            const res = Store.deleteUser(u.id);
                            if (res.ok) { toast.show('Account removed'); }
                            else toast.show(res.error, { bad: true });
                          }
                        }}>Remove</Button>
                      )}
                    </div>
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </Card>

      <StaffEditModal user={editing} onClose={() => setEditing(null)}
        onCreated={(u, pwd) => setCreds({ user: u, password: pwd, mode: 'created' })} />
      <CredsModal data={creds} onClose={() => setCreds(null)} />
    </div>
  );
}

function StaffEditModal({ user, onClose, onCreated }) {
  const toast = useToast();
  const roles = useStore(s => s.roles);
  const isNew = user && !user.id;
  const [form, setForm] = React.useState(null);
  const [customPwd, setCustomPwd] = React.useState('');
  React.useEffect(() => {
    if (!user) { setForm(null); setCustomPwd(''); return; }
    setForm(user.id
      ? { id: user.id, name: user.name, role: user.role, property: user.property, email: user.email, bio: user.bio || '' }
      : { name: '', role: 'Associate', property: 'Biratchowk', email: '', bio: '' });
    setCustomPwd('');
  }, [user]);
  if (!user || !form) return null;
  const set = (k, v) => setForm(f => Object.assign({}, f, { [k]: v }));

  function save() {
    if (!form.name.trim()) return toast.show('Name is required', { bad: true });
    if (isNew) {
      const payload = { name: form.name.trim(), role: form.role, property: form.property, email: form.email, bio: form.bio };
      if (customPwd && customPwd.length >= 8) payload.password = customPwd;
      const res = Store.addUser(payload);
      if (!res.ok) return toast.show(res.error, { bad: true });
      onCreated(res.user, res.password);
      onClose();
    } else {
      const res = Store.updateUser(form.id, { name: form.name.trim(), role: form.role, property: form.property, email: form.email, bio: form.bio });
      if (!res.ok) return toast.show(res.error, { bad: true });
      toast.show('Staff updated');
      onClose();
    }
  }

  const perms = (window.ROLE_PERMS && window.ROLE_PERMS[form.role]) || [];
  const permLabels = (window.ALL_PERMS || []).filter(p => perms.includes(p.id));
  const permGroups = permLabels.reduce((acc, p) => { (acc[p.group] = acc[p.group] || []).push(p); return acc; }, {});

  return (
    <Modal open title={isNew ? 'Add new staff member' : 'Edit · ' + form.name} onClose={onClose} width={620} footer={
      <>
        <Button variant="ghost" onClick={onClose}>Cancel</Button>
        <Button variant="gold" onClick={save}>{isNew ? '+ Create account' : 'Save changes'}</Button>
      </>
    }>
      {isNew && (
        <div className="pt-mute" style={{ fontSize: 13, marginBottom: 14, padding: 12,
          background: 'rgba(200,162,90,0.06)', border: '1px solid ' + T.goldBorder, borderRadius: 8 }}>
          You're creating a login account. Email auto-generates from the name; you can override.
          Set a password below — leave blank for a strong auto-generated one (shown once after save).
        </div>
      )}
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
        <Input label="Full name *" value={form.name} onChange={v => set('name', v)} placeholder="e.g. Anil Sharma" />
        <Select label="Role *" value={form.role} onChange={v => set('role', v)} options={roles} />
        <Select label="Property" value={form.property} onChange={v => set('property', v)} options={['Biratchowk', 'Damak']} />
        <Input label="Email" value={form.email} onChange={v => set('email', v)}
          placeholder={isNew && form.name ? form.name.toLowerCase().replace(/\s+/g, '.') + '@nepalirika.com' : 'name@nepalirika.com'}
          hint={isNew ? 'leave blank to auto-generate from name' : ''} />
        <Input label="Job title / note" value={form.bio} onChange={v => set('bio', v)} placeholder="e.g. Front-desk lead" />
        {isNew && (
          <Input label="Initial password (optional)" value={customPwd} onChange={setCustomPwd} type="password"
            placeholder="leave blank to auto-generate" hint="min 8 chars · they should change on first login" />
        )}
      </div>
      {!isNew && (
        <div className="pt-faint" style={{ fontSize: 12, marginTop: 14, padding: 10, background: 'rgba(255,255,255,0.03)', borderRadius: 8 }}>
          ↳ To reset the password, click "Reset password" on the staff row (closes this dialog first).
        </div>
      )}

      <div style={{ marginTop: 14, padding: 12, background: 'rgba(255,255,255,0.03)', borderRadius: 10 }}>
        <div className="pt-eyebrow" style={{ marginBottom: 8 }}>{form.role} can do:</div>
        {form.role === 'Super Admin' ? (
          <div className="pt-mute" style={{ fontSize: 12 }}>Everything · including manage staff, edit audit log, change settings.</div>
        ) : Object.keys(permGroups).length === 0 ? (
          <div className="pt-faint" style={{ fontSize: 12 }}>No permissions assigned. Edit role in Roles screen.</div>
        ) : (
          <div className="pt-col" style={{ gap: 8 }}>
            {Object.entries(permGroups).map(([g, ps]) => (
              <div key={g}>
                <div className="pt-faint" style={{ fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 2 }}>{g}</div>
                <div className="pt-mute" style={{ fontSize: 12 }}>{ps.map(p => p.label).join(' · ')}</div>
              </div>
            ))}
          </div>
        )}
      </div>
    </Modal>
  );
}

function CredsModal({ data, onClose }) {
  const toast = useToast();
  if (!data) return null;
  const { user, password, mode } = data;
  const copy = (text) => {
    try { navigator.clipboard.writeText(text); toast.show('Copied'); }
    catch { toast.show('Copy failed', { bad: true }); }
  };
  return (
    <Modal open title={mode === 'reset' ? `New password for ${user.name}` : `Account ready · ${user.name}`} onClose={onClose} width={460} footer={
      <>
        <Button onClick={() => copy(`${user.email}\n${password}`)}>⧉ Copy both</Button>
        <div style={{ flex: 1 }} />
        <Button variant="gold" onClick={onClose}>Done</Button>
      </>
    }>
      <div className="pt-mute" style={{ fontSize: 13, marginBottom: 14 }}>
        {mode === 'reset'
          ? 'Share these with the staff member. The old password no longer works.'
          : 'Account created · share these credentials securely. They will not be shown again.'}
      </div>
      <div className="pt-col" style={{ gap: 10 }}>
        <CredRow label="Login email" value={user.email} onCopy={copy} />
        <CredRow label="Password" value={password} onCopy={copy} mono />
      </div>
      <div className="pt-faint" style={{ fontSize: 11, marginTop: 14 }}>
        Tell them to change it from <b>Your session · Change password</b> after first sign-in.
      </div>
    </Modal>
  );
}

function CredRow({ label, value, onCopy, mono }) {
  return (
    <div style={{ padding: 12, background: 'rgba(255,255,255,0.03)', border: '1px solid ' + T.borderSoft, borderRadius: 8 }}>
      <div className="pt-eyebrow" style={{ marginBottom: 6 }}>{label}</div>
      <div className="pt-row" style={{ gap: 10 }}>
        <div style={{ flex: 1, fontWeight: 600, fontSize: 15, fontFamily: mono ? 'ui-monospace, monospace' : 'inherit', wordBreak: 'break-all' }}>{value}</div>
        <Button size="sm" onClick={() => onCopy(value)}>Copy</Button>
      </div>
    </div>
  );
}

// Error boundary — catches any component crash and shows a readable error
// instead of a black screen. Crucially, lets the user navigate away or reload
// without losing their session.
class ErrorBoundary extends React.Component {
  constructor(props) { super(props); this.state = { err: null, info: null }; }
  static getDerivedStateFromError(err) { return { err }; }
  componentDidCatch(err, info) {
    console.error('[ErrorBoundary] component crashed:', err, info);
    this.setState({ info });
  }
  render() {
    if (!this.state.err) return this.props.children;
    return (
      <div style={{ minHeight: '100vh', padding: 30, background: '#0a1424', color: '#e9ecf3', fontFamily: 'Poppins, system-ui, sans-serif' }}>
        <div style={{ maxWidth: 720, margin: '40px auto' }}>
          <div style={{ fontSize: 13, color: '#fca5a5', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 8 }}>⚠ Something broke on this page</div>
          <h1 style={{ fontFamily: 'Cormorant Garamond, serif', fontSize: 36, fontWeight: 600, margin: '0 0 14px' }}>
            We caught the error so you don't see a black screen.
          </h1>
          <p style={{ color: '#94a3b9', fontSize: 14, lineHeight: 1.6, marginBottom: 14 }}>
            Your session is safe. Most likely a member record is missing some fields, or a network call failed.
            Try one of the buttons below.
          </p>
          <pre style={{
            background: 'rgba(239,68,68,0.08)', border: '1px solid rgba(239,68,68,0.3)',
            borderRadius: 8, padding: 14, fontSize: 12, overflowX: 'auto',
            color: '#fca5a5', whiteSpace: 'pre-wrap',
          }}>
{(this.state.err && (this.state.err.stack || this.state.err.message)) || String(this.state.err)}
          </pre>
          <div style={{ marginTop: 18, display: 'flex', gap: 10, flexWrap: 'wrap' }}>
            <button onClick={() => { this.setState({ err: null, info: null }); window.location.hash = '#/dashboard'; }}
              style={{ padding: '10px 16px', background: 'linear-gradient(180deg, #e0bb70, #c8a25a)', color: '#1a1207', border: 0, borderRadius: 8, fontWeight: 600, cursor: 'pointer' }}>
              ← Back to dashboard
            </button>
            <button onClick={() => { window.location.reload(); }}
              style={{ padding: '10px 16px', background: 'rgba(255,255,255,0.06)', color: '#e9ecf3', border: '1px solid rgba(255,255,255,0.18)', borderRadius: 8, cursor: 'pointer' }}>
              Reload the app
            </button>
            <button onClick={() => {
              if (window.Store && window.Store.hydrate) window.Store.hydrate();
              this.setState({ err: null, info: null });
            }}
              style={{ padding: '10px 16px', background: 'rgba(255,255,255,0.06)', color: '#e9ecf3', border: '1px solid rgba(255,255,255,0.18)', borderRadius: 8, cursor: 'pointer' }}>
              Reload data from server
            </button>
            <button onClick={() => { try { localStorage.removeItem('nepalirika.portal.v1'); } catch {} window.location.reload(); }}
              style={{ padding: '10px 16px', background: 'rgba(239,68,68,0.08)', color: '#fca5a5', border: '1px solid rgba(239,68,68,0.3)', borderRadius: 8, cursor: 'pointer' }}>
              Wipe local cache + reload (last resort)
            </button>
          </div>
        </div>
      </div>
    );
  }
}

function AppInner() {
  const route = useRoute();
  const currentUserId = useStore(s => s.currentUserId);
  const [mobileNavOpen, setMobileNavOpen] = React.useState(false);
  const { notify } = useNotify();
  React.useEffect(() => { window._appNotify = notify; }, [notify]);

  // Close mobile nav on route change
  React.useEffect(() => { setMobileNavOpen(false); }, [route.hash]);

  // ─── Session auto-logout after 15 minutes idle ───
  React.useEffect(() => {
    if (!currentUserId) return;
    let timer;
    const reset = () => {
      clearTimeout(timer);
      timer = setTimeout(() => {
        Store.logout();
        notify('You were signed out after 15 minutes of inactivity. Please sign in again.', { icon: 'lock', title: 'Session expired' });
      }, 15 * 60 * 1000);
    };
    const events = ['mousemove', 'keydown', 'click', 'scroll', 'touchstart'];
    events.forEach(e => window.addEventListener(e, reset, { passive: true }));
    reset();
    return () => {
      clearTimeout(timer);
      events.forEach(e => window.removeEventListener(e, reset));
    };
  }, [currentUserId]);

  return (
    <ToastProvider>
      {!currentUserId ? (
        <LoginPage />
      ) : (
        <div className="pt pt-app" style={{ display: 'flex', height: '100vh', overflow: 'hidden', position: 'relative' }}>
          {/* Mobile overlay when nav is open */}
          {mobileNavOpen && (
            <div onClick={() => setMobileNavOpen(false)}
              className="pt-nav-overlay"
              style={{
                position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.5)', zIndex: 80,
                display: 'none',
              }} />
          )}
          <div className={'pt-sidebar-wrap' + (mobileNavOpen ? ' pt-nav-open' : '')}>
            <Sidebar currentPath={route.path} />
          </div>
          <main style={{ flex: 1, display: 'flex', flexDirection: 'column', minWidth: 0 }}>
            <Topbar onHamburger={() => setMobileNavOpen(v => !v)} />
            <SyncErrorBanner />
            <div id="pt-content" style={{ flex: 1, overflowY: 'auto', padding: '22px 22px' }}>
              <ErrorBoundary key={route.hash}><Router /></ErrorBoundary>
              <div style={{ height: 40 }} />
            </div>
          </main>
        </div>
      )}
    </ToastProvider>
  );
}

function App() {
  return (
    <NotifyProvider>
      <ConfirmProvider>
        <AppInner />
      </ConfirmProvider>
    </NotifyProvider>
  );
}

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