// Settings panes (Account / Team / Integrations / Tommy AI / Notifications)
// and the SettingsView container. All wired to real endpoints.

// ---- Settings (Account / Team / Integrations / Tommy AI / Notifications) ---

// Fallback used when /auth/role-config hasn't loaded yet or the request fails.
function pageAccessFor(role) {
  switch ((role || 'staff').toLowerCase()) {
    case 'admin':      return ['home', 'leads', 'analytics', 'calendar', 'tommy', 'settings', 'members'];
    case 'front_desk': return ['home', 'leads', 'calendar', 'tommy', 'members'];
    case 'trainer':    return ['home', 'calendar', 'members'];
    case 'staff':
    default:           return ['home', 'leads', 'calendar', 'tommy'];
  }
}

function prettyPageLabel(id) {
  return String(id || '').replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
}

function SettingsToggle({ on, onClick, disabled }) {
  return (
    <button
      type="button"
      className={'settings-toggle' + (on ? ' on' : '')}
      onClick={onClick}
      disabled={disabled}
      aria-pressed={!!on}
    >
      <span className="settings-toggle-thumb" />
    </button>
  );
}

function AccountPane({ user, onSignOut }) {
  const [name, setName] = React.useState(user?.name || '');
  const [editingName, setEditingName] = React.useState(false);
  const [saving, setSaving] = React.useState(false);

  React.useEffect(() => { if (user?.name) setName(user.name); }, [user?.name]);

  const roleCfg = useAsync(() => api.get('/api/v2/auth/role-config').catch(() => null), []);
  const pages = (roleCfg.data?.nav && Array.isArray(roleCfg.data.nav) && roleCfg.data.nav.length > 0)
    ? roleCfg.data.nav
    : pageAccessFor(user?.role);

  async function saveName(raw) {
    const next = (raw || '').trim();
    if (!next || next === name) { setEditingName(false); return; }
    setSaving(true);
    try {
      const r = await api.patch('/api/v2/auth/update-profile', { name: next });
      setName(r.name || next);
      if (window.CURRENT_USER) window.CURRENT_USER.name = r.name || next;
      setEditingName(false);
    } catch (e) { alert(e.message || 'Save failed'); }
    finally { setSaving(false); }
  }

  async function changePassword() {
    const current = prompt('Current password');
    if (!current) return;
    const next = prompt('New password (min 8 chars)');
    if (!next) return;
    try {
      await api.post('/api/v2/auth/change-password', { current_password: current, new_password: next });
      alert('Password updated.');
    } catch (e) { alert(e.message || 'Change failed'); }
  }

  async function signOutAll() {
    if (!confirm('This revokes every active session for your account. Continue?')) return;
    try {
      await api.post('/api/v2/auth/sign-out-all', {});
      api.clearTokens();
      window.location.reload();
    } catch (e) { alert(e.message || 'Failed'); }
  }

  return (
    <>
      <div className="settings-card">
        <div className="settings-card-label">Profile</div>

        <div className="settings-field">
          <div className="settings-field-row">
            <span className="settings-field-label">Name</span>
            {!editingName && (
              <button type="button" className="settings-edit-btn" onClick={() => setEditingName(true)}>Edit</button>
            )}
          </div>
          {editingName ? (
            <input
              autoFocus
              type="text"
              defaultValue={name}
              disabled={saving}
              className="settings-input"
              onKeyDown={e => {
                if (e.key === 'Enter') saveName(e.currentTarget.value);
                if (e.key === 'Escape') setEditingName(false);
              }}
              onBlur={e => saveName(e.currentTarget.value)}
            />
          ) : (
            <div className="settings-field-value">{name || '—'}</div>
          )}
        </div>

        <div className="settings-field">
          <div className="settings-field-label">Email</div>
          <div className="settings-field-value">{user?.email || '—'}</div>
        </div>

        <div className="settings-field">
          <div className="settings-field-row">
            <span className="settings-field-label">Role</span>
            <span className="settings-active-badge">Active</span>
          </div>
          <div className="settings-field-value settings-cap">{user?.role || 'staff'}</div>
        </div>

        <div className="settings-field">
          <div className="settings-field-label">Page access</div>
          <div className="settings-pills">
            {pages.map(p => <span key={p} className="settings-pill">{prettyPageLabel(p)}</span>)}
          </div>
        </div>

        <button type="button" className="btn" style={{marginTop:10}} onClick={changePassword}>
          Change password
        </button>
      </div>

      <div className="settings-card danger">
        <div className="settings-card-label">Danger zone</div>
        <div className="settings-danger-row">
          <div>
            <div className="settings-danger-title">Sign out everywhere</div>
            <div className="settings-danger-sub">Revokes all active tokens</div>
          </div>
          <button type="button" className="btn danger" onClick={signOutAll}>Sign out all</button>
        </div>
        <div className="settings-danger-row">
          <div>
            <div className="settings-danger-title">Sign out this browser</div>
            <div className="settings-danger-sub">Drops this session only</div>
          </div>
          <button type="button" className="btn" onClick={onSignOut}>Sign out</button>
        </div>
      </div>
    </>
  );
}

// Mirrors `ALLOWED_ROLES` in app/auth.py:22 (and `available_roles` from
// /api/v2/auth/role-config). Order matches the backend tuple so the dropdown
// matches the canonical list — drift between the two sources is what shipped
// privilege bugs in PR #3 (the inline role list in leads.py rejected the
// "staff" role that auth.py accepted). Server-driven sourcing is the
// long-term fix; this constant is the safe-on-page-load fallback for when
// /role-config hasn't returned yet (modal opens before the request lands).
const STAFF_ROLES = ['admin', 'front_desk', 'trainer', 'staff'];
const ROLE_DESCRIPTIONS = {
  admin:      'Full access: home, leads, analytics, calendar, tommy, settings, members',
  front_desk: 'Front desk: home, leads, calendar, tommy, members',
  trainer:    'Trainer: home, calendar, members',
  staff:      'Default staff: home, leads, calendar, tommy',
};

// Modal to create a new staff user. Posts to /api/v2/admin/users (admin-only),
// then surfaces the server-generated temporary password in a dedicated success
// view with copy-to-clipboard so it doesn't get lost in an alert() the user
// might dismiss before reading.
function InviteStaffModal({ open, onClose, onCreated }) {
  const [mode, setMode] = React.useState('form'); // 'form' | 'success'
  const [email, setEmail] = React.useState('');
  const [name, setName] = React.useState('');
  const [role, setRole] = React.useState('staff');
  const [submitting, setSubmitting] = React.useState(false);
  const [error, setError] = React.useState(null);
  const [created, setCreated] = React.useState(null);
  const [copied, setCopied] = React.useState(false);
  const firstFieldRef = React.useRef(null);

  // Reset state and focus email field whenever the modal opens.
  React.useEffect(() => {
    if (!open) return;
    setMode('form');
    setEmail(''); setName(''); setRole('staff');
    setError(null); setSubmitting(false);
    setCreated(null); setCopied(false);
    setTimeout(() => firstFieldRef.current?.focus(), 0);
  }, [open]);

  React.useEffect(() => {
    if (!open) return;
    function onKey(e) { if (e.key === 'Escape' && !submitting) handleClose(); }
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [open, submitting, mode, created]);

  if (!open) return null;

  function handleClose() {
    if (mode === 'success' && onCreated) onCreated();
    onClose();
  }

  const canSubmit = email.trim() && name.trim() && !submitting;

  async function submit(e) {
    e?.preventDefault();
    if (!canSubmit) return;
    setSubmitting(true);
    setError(null);
    try {
      const payload = {
        email: email.trim().toLowerCase(),
        name: name.trim(),
        role,
      };
      const res = await api.post('/api/v2/admin/users', payload);
      setCreated({
        email: res.email || payload.email,
        name: res.name || payload.name,
        role: res.role || payload.role,
        temp_password: res.temp_password || '',
      });
      setMode('success');
    } catch (err) {
      setError(err?.message || 'Failed to create user');
    } finally {
      setSubmitting(false);
    }
  }

  async function copyPassword() {
    if (!created?.temp_password) return;
    try {
      await navigator.clipboard.writeText(created.temp_password);
      setCopied(true);
      setTimeout(() => setCopied(false), 1800);
    } catch (e) {
      // Clipboard may be blocked on http or in the browser config; the
      // password stays visible so the admin can still copy it manually.
    }
  }

  return (
    <div
      className="lead-modal-backdrop"
      onMouseDown={(e) => { if (e.target === e.currentTarget && !submitting) handleClose(); }}
      role="dialog"
      aria-modal="true"
      aria-labelledby="invite-modal-title"
    >
      {mode === 'form' && (
        <form className="lead-modal-card" onSubmit={submit}>
          <div className="lead-modal-hd">
            <div id="invite-modal-title" className="lead-modal-title">— Invite staff</div>
            <button
              type="button"
              className="lead-modal-close"
              onClick={handleClose}
              disabled={submitting}
              aria-label="Close"
            >×</button>
          </div>

          <div className="lead-modal-bd">
            <label className="lead-modal-field">
              <span className="lead-modal-lbl">Email <span className="req">*</span></span>
              <input
                ref={firstFieldRef}
                type="email"
                value={email}
                onChange={(e) => setEmail(e.target.value)}
                placeholder="jane@rawathletic.club"
                required
                autoComplete="off"
                disabled={submitting}
              />
            </label>

            <label className="lead-modal-field">
              <span className="lead-modal-lbl">Name <span className="req">*</span></span>
              <input
                type="text"
                value={name}
                onChange={(e) => setName(e.target.value)}
                placeholder="Jane Doe"
                required
                disabled={submitting}
              />
            </label>

            <label className="lead-modal-field">
              <span className="lead-modal-lbl">Role</span>
              <select
                value={role}
                onChange={(e) => setRole(e.target.value)}
                disabled={submitting}
              >
                {STAFF_ROLES.map(r => <option key={r} value={r}>{r}</option>)}
              </select>
              <div style={{fontSize:11, color:'var(--muted)', marginTop:4}}>
                {ROLE_DESCRIPTIONS[role]}
              </div>
            </label>

            <div style={{fontSize:12, color:'var(--muted)'}}>
              A temporary password will be generated and shown next. Share it
              with the new user through a trusted channel — it can't be
              retrieved later.
            </div>

            {error && <div className="lead-modal-err">{error}</div>}
          </div>

          <div className="lead-modal-ft">
            <button
              type="button"
              className="btn ghost sm"
              onClick={handleClose}
              disabled={submitting}
            >Cancel</button>
            <button
              type="submit"
              className="btn primary"
              disabled={!canSubmit}
            >{submitting ? 'Creating…' : 'Create user'}</button>
          </div>
        </form>
      )}

      {mode === 'success' && created && (
        <div className="lead-modal-card">
          <div className="lead-modal-hd">
            <div id="invite-modal-title" className="lead-modal-title">— User created</div>
            <button
              type="button"
              className="lead-modal-close"
              onClick={handleClose}
              aria-label="Close"
            >×</button>
          </div>

          <div className="lead-modal-bd">
            <div className="lead-modal-field">
              <span className="lead-modal-lbl">Email</span>
              <div className="settings-field-value mono">{created.email}</div>
            </div>
            <div className="lead-modal-field">
              <span className="lead-modal-lbl">Name</span>
              <div className="settings-field-value">{created.name}</div>
            </div>
            <div className="lead-modal-field">
              <span className="lead-modal-lbl">Role</span>
              <div className="settings-field-value settings-cap">{created.role}</div>
            </div>
            <div className="lead-modal-field">
              <span className="lead-modal-lbl">Temporary password</span>
              <div style={{display:'flex', gap:8, alignItems:'center'}}>
                <code
                  className="mono"
                  style={{
                    flex:1,
                    padding:'8px 10px',
                    background:'var(--bone-2)',
                    borderRadius:6,
                    fontSize:13,
                    wordBreak:'break-all',
                    userSelect:'all',
                  }}
                >{created.temp_password || '—'}</code>
                <button
                  type="button"
                  className="btn sm"
                  onClick={copyPassword}
                  disabled={!created.temp_password}
                >{copied ? 'Copied!' : 'Copy'}</button>
              </div>
              <div style={{fontSize:11, color:'var(--muted)', marginTop:6}}>
                Stored as a one-way hash on the server. Once this dialog closes,
                the password can't be retrieved — you'd have to issue a new one
                via Reset pw.
              </div>
            </div>

            <div
              style={{
                marginTop:14,
                padding:'12px 14px',
                background:'var(--bone-2)',
                borderRadius:8,
                fontSize:12,
                lineHeight:1.55,
              }}
            >
              <div
                style={{
                  fontSize:10,
                  fontWeight:600,
                  letterSpacing:0.6,
                  textTransform:'uppercase',
                  color:'var(--ink)',
                  marginBottom:8,
                }}
              >Next steps</div>
              <ol style={{margin:0, paddingLeft:18, color:'var(--muted)'}}>
                <li>
                  <strong style={{color:'var(--ink)'}}>Share the credentials</strong>{' '}
                  with {created.name || 'the new user'} through a trusted channel —
                  Telegram, Signal, or in person. The system does not email them
                  automatically.
                </li>
                <li style={{marginTop:6}}>
                  Tell them to sign in at{' '}
                  <span className="mono" style={{color:'var(--ink)'}}>{window.location.origin}</span>{' '}
                  with their email and the temporary password above.
                </li>
                <li style={{marginTop:6}}>
                  Right after signing in, they should open{' '}
                  <strong style={{color:'var(--ink)'}}>Settings → Account → Change password</strong>{' '}
                  and pick their own.
                </li>
                <li style={{marginTop:6}}>
                  If they lose the temporary password before changing it,
                  come back here and click{' '}
                  <strong style={{color:'var(--ink)'}}>Reset pw</strong>{' '}
                  on their row to generate a new one.
                </li>
              </ol>
            </div>
          </div>

          <div className="lead-modal-ft">
            <button
              type="button"
              className="btn primary"
              onClick={handleClose}
            >Done</button>
          </div>
        </div>
      )}
    </div>
  );
}

// Cryptographically random password for the reset flow. Excludes ambiguous
// glyphs (0/O, 1/l/I) to reduce mistakes when the admin transcribes the
// password to Telegram or reads it out loud. crypto.getRandomValues is the
// only randomness source — Math.random is not safe for credentials.
function generateTempPassword(length = 16) {
  const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789!@#$%';
  const arr = new Uint32Array(length);
  crypto.getRandomValues(arr);
  let result = '';
  for (let i = 0; i < length; i++) result += chars[arr[i] % chars.length];
  return result;
}

// Two-step reset: confirm-with-context, then issue a freshly-generated
// password and surface it to the admin with copy-to-clipboard. Same UX
// shape as InviteStaffModal so the Team page feels coherent.
function ResetPasswordModal({ member, onClose, onDone }) {
  const [mode, setMode] = React.useState('confirm'); // 'confirm' | 'success'
  const [submitting, setSubmitting] = React.useState(false);
  const [error, setError] = React.useState(null);
  const [newPw, setNewPw] = React.useState('');
  const [copied, setCopied] = React.useState(false);

  React.useEffect(() => {
    if (!member) return;
    setMode('confirm');
    setSubmitting(false);
    setError(null);
    setNewPw('');
    setCopied(false);
  }, [member?.id]);

  React.useEffect(() => {
    if (!member) return;
    function onKey(e) { if (e.key === 'Escape' && !submitting) handleClose(); }
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [member, submitting, mode]);

  if (!member) return null;

  function handleClose() {
    if (mode === 'success' && onDone) onDone();
    onClose();
  }

  async function confirm() {
    setSubmitting(true);
    setError(null);
    const pw = generateTempPassword();
    try {
      await api.patch(`/api/v2/admin/users/${member.id}`, { password: pw });
      setNewPw(pw);
      setMode('success');
    } catch (err) {
      setError(err?.message || 'Reset failed');
    } finally {
      setSubmitting(false);
    }
  }

  async function copyPassword() {
    if (!newPw) return;
    try {
      await navigator.clipboard.writeText(newPw);
      setCopied(true);
      setTimeout(() => setCopied(false), 1800);
    } catch (e) { /* clipboard blocked — code stays selectable */ }
  }

  return (
    <div
      className="lead-modal-backdrop"
      onMouseDown={(e) => { if (e.target === e.currentTarget && !submitting) handleClose(); }}
      role="dialog"
      aria-modal="true"
      aria-labelledby="reset-modal-title"
    >
      {mode === 'confirm' && (
        <div className="lead-modal-card">
          <div className="lead-modal-hd">
            <div id="reset-modal-title" className="lead-modal-title">— Reset password</div>
            <button type="button" className="lead-modal-close" onClick={handleClose} disabled={submitting} aria-label="Close">×</button>
          </div>
          <div className="lead-modal-bd">
            <div className="lead-modal-field">
              <span className="lead-modal-lbl">User</span>
              <div className="settings-field-value">
                <strong>{member.name || member.email.split('@')[0]}</strong>
                <span className="mono" style={{marginLeft:8, color:'var(--muted)'}}>{member.email}</span>
              </div>
            </div>
            <div style={{fontSize:13, color:'var(--muted)', lineHeight:1.55, marginTop:6}}>
              A new temporary password will be generated and shown next.
              The user's existing password stops working immediately. Their
              currently-active sessions are <strong>not</strong> revoked by this —
              an admin token they've already minted keeps working until expiry
              unless you also bump <span className="mono">token_version</span>.
            </div>
            {error && <div className="lead-modal-err">{error}</div>}
          </div>
          <div className="lead-modal-ft">
            <button type="button" className="btn ghost sm" onClick={handleClose} disabled={submitting}>Cancel</button>
            <button type="button" className="btn primary" onClick={confirm} disabled={submitting}>
              {submitting ? 'Resetting…' : 'Reset password'}
            </button>
          </div>
        </div>
      )}

      {mode === 'success' && (
        <div className="lead-modal-card">
          <div className="lead-modal-hd">
            <div id="reset-modal-title" className="lead-modal-title">— Password reset</div>
            <button type="button" className="lead-modal-close" onClick={handleClose} aria-label="Close">×</button>
          </div>
          <div className="lead-modal-bd">
            <div className="lead-modal-field">
              <span className="lead-modal-lbl">User</span>
              <div className="settings-field-value mono">{member.email}</div>
            </div>
            <div className="lead-modal-field">
              <span className="lead-modal-lbl">New temporary password</span>
              <div style={{display:'flex', gap:8, alignItems:'center'}}>
                <code
                  className="mono"
                  style={{
                    flex:1,
                    padding:'8px 10px',
                    background:'var(--bone-2)',
                    borderRadius:6,
                    fontSize:13,
                    wordBreak:'break-all',
                    userSelect:'all',
                  }}
                >{newPw}</code>
                <button type="button" className="btn sm" onClick={copyPassword}>
                  {copied ? 'Copied!' : 'Copy'}
                </button>
              </div>
              <div style={{fontSize:11, color:'var(--muted)', marginTop:6}}>
                The previous password no longer works. Once this dialog closes,
                this temporary password can't be retrieved — you'd have to
                reset again.
              </div>
            </div>

            <div
              style={{
                marginTop:14,
                padding:'12px 14px',
                background:'var(--bone-2)',
                borderRadius:8,
                fontSize:12,
                lineHeight:1.55,
              }}
            >
              <div style={{
                fontSize:10,
                fontWeight:600,
                letterSpacing:0.6,
                textTransform:'uppercase',
                color:'var(--ink)',
                marginBottom:8,
              }}>Next steps</div>
              <ol style={{margin:0, paddingLeft:18, color:'var(--muted)'}}>
                <li>
                  <strong style={{color:'var(--ink)'}}>Share the new password</strong>{' '}
                  with {member.name || 'the user'} through a trusted channel —
                  Telegram, Signal, or in person.
                </li>
                <li style={{marginTop:6}}>
                  Ask them to sign in at{' '}
                  <span className="mono" style={{color:'var(--ink)'}}>{window.location.origin}</span>{' '}
                  with this password and immediately set their own via{' '}
                  <strong style={{color:'var(--ink)'}}>Settings → Account → Change password</strong>.
                </li>
              </ol>
            </div>
          </div>
          <div className="lead-modal-ft">
            <button type="button" className="btn primary" onClick={handleClose}>Done</button>
          </div>
        </div>
      )}
    </div>
  );
}

// Single-step destructive confirm with the user's context (name, email, role)
// and an explicit warning about what happens. Uses .btn.danger so it reads as
// red/warn instead of looking like the same neutral confirm as Cancel.
function RemoveUserModal({ member, onClose, onRemoved }) {
  const [submitting, setSubmitting] = React.useState(false);
  const [error, setError] = React.useState(null);

  React.useEffect(() => {
    if (!member) return;
    setSubmitting(false);
    setError(null);
  }, [member?.id]);

  React.useEffect(() => {
    if (!member) return;
    function onKey(e) { if (e.key === 'Escape' && !submitting) onClose(); }
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, [member, submitting, onClose]);

  if (!member) return null;

  async function confirm() {
    setSubmitting(true);
    setError(null);
    try {
      await api.del(`/api/v2/admin/users/${member.id}`);
      if (onRemoved) onRemoved();
      onClose();
    } catch (err) {
      setError(err?.message || 'Remove failed');
      setSubmitting(false);
    }
  }

  return (
    <div
      className="lead-modal-backdrop"
      onMouseDown={(e) => { if (e.target === e.currentTarget && !submitting) onClose(); }}
      role="dialog"
      aria-modal="true"
      aria-labelledby="remove-modal-title"
    >
      <div className="lead-modal-card">
        <div className="lead-modal-hd">
          <div id="remove-modal-title" className="lead-modal-title">— Remove team member</div>
          <button type="button" className="lead-modal-close" onClick={onClose} disabled={submitting} aria-label="Close">×</button>
        </div>
        <div className="lead-modal-bd">
          <div className="lead-modal-field">
            <span className="lead-modal-lbl">User</span>
            <div className="settings-field-value" style={{display:'flex', alignItems:'center', flexWrap:'wrap', gap:8}}>
              <strong>{member.name || member.email.split('@')[0]}</strong>
              <span className="mono" style={{color:'var(--muted)'}}>{member.email}</span>
              <span className={'settings-role-pill' + (member.role === 'admin' ? ' admin' : '')}>
                {member.role || 'staff'}
              </span>
            </div>
          </div>
          <div style={{fontSize:13, color:'var(--muted)', lineHeight:1.55, marginTop:6}}>
            This deletes the staff record from this dashboard's user table. They
            will <strong>not</strong> be removed from GoHighLevel — contacts and
            SMS history are unaffected.
          </div>
          <div
            style={{
              marginTop:12,
              padding:'10px 12px',
              background:'var(--warn-soft)',
              borderLeft:'3px solid var(--warn)',
              borderRadius:4,
              fontSize:12,
              color:'var(--ink)',
              lineHeight:1.5,
            }}
          >
            <strong>This action cannot be undone.</strong> If you remove the
            wrong user, you'll have to recreate them from scratch with a new
            password.
          </div>
          {error && <div className="lead-modal-err" style={{marginTop:10}}>{error}</div>}
        </div>
        <div className="lead-modal-ft">
          <button type="button" className="btn ghost sm" onClick={onClose} disabled={submitting}>Cancel</button>
          <button type="button" className="btn danger" onClick={confirm} disabled={submitting}>
            {submitting ? 'Removing…' : `Remove ${member.name || 'user'}`}
          </button>
        </div>
      </div>
    </div>
  );
}

function TeamPane({ user }) {
  const isAdmin = user?.role === 'admin';
  const staff = useAsync(() => isAdmin ? api.get('/api/v2/staff') : Promise.resolve([]), [isAdmin]);
  const list = Array.isArray(staff.data) ? staff.data : [];
  const [editingId, setEditingId] = React.useState(null);
  const [draft, setDraft] = React.useState({ name: '', role: 'staff' });
  const [saving, setSaving] = React.useState(false);
  const [editError, setEditError] = React.useState(null);
  const [inviteOpen, setInviteOpen] = React.useState(false);
  const [resetTarget, setResetTarget] = React.useState(null);
  const [removeTarget, setRemoveTarget] = React.useState(null);

  function startEdit(s) {
    setEditingId(s.id);
    setDraft({ name: s.name || '', role: s.role || 'staff' });
    setEditError(null);
  }
  function cancelEdit() { setEditingId(null); setEditError(null); }

  async function saveEdit(id) {
    const next = { name: draft.name.trim(), role: draft.role };
    // The Save button is disabled when name is empty so this is unreachable
    // through the UI, but keep a guard for keyboard/programmatic submit.
    if (!next.name) { setEditError('Name cannot be empty.'); return; }
    setSaving(true);
    setEditError(null);
    try {
      await api.patch(`/api/v2/admin/users/${id}`, next);
      await staff.reload();
      setEditingId(null);
    } catch (e) {
      setEditError(e.message || 'Save failed');
    } finally {
      setSaving(false);
    }
  }

  if (!isAdmin) {
    return (
      <div className="settings-card">
        <div className="settings-card-label">Team</div>
        <div className="settings-empty">Admin only. Ask an admin to grant access.</div>
      </div>
    );
  }

  return (
    <div className="settings-card">
      <div className="settings-card-hd">
        <span className="settings-card-label">Team · {list.length}</span>
        <button type="button" className="btn primary sm" onClick={() => setInviteOpen(true)}>+ Invite staff</button>
      </div>
      <InviteStaffModal
        open={inviteOpen}
        onClose={() => setInviteOpen(false)}
        onCreated={() => staff.reload()}
      />
      <ResetPasswordModal
        member={resetTarget}
        onClose={() => setResetTarget(null)}
      />
      <RemoveUserModal
        member={removeTarget}
        onClose={() => setRemoveTarget(null)}
        onRemoved={() => staff.reload()}
      />
      <div className="settings-table-wrap">
        <table className="tbl">
          <thead>
            <tr>
              <th style={{paddingLeft:16}}>Name</th>
              <th>Role</th>
              <th>Email</th>
              <th>Created</th>
              <th style={{textAlign:'right', paddingRight:16}}>&nbsp;</th>
            </tr>
          </thead>
          <tbody>
            {list.length === 0 && !staff.loading && (
              <tr><td colSpan="5"><div className="settings-empty">No staff records.</div></td></tr>
            )}
            {list.map(s => {
              const b = staffBadge(s.name || s.email);
              const isSelf = String(s.id) === String(user?.id);
              const isEditing = editingId === s.id;
              return (
                <React.Fragment key={s.id}>
                  <tr>
                    <td className="name" style={{paddingLeft:16}}>
                      <div style={{display:'flex', alignItems:'center', gap:10}}>
                        <span className="avatar" style={{width:26, height:26, fontSize:10, background:b.color, flexShrink:0}}>{b.initials}</span>
                        {isEditing ? (
                          <div style={{display:'flex', flexDirection:'column', flex:1, minWidth:0, gap:3}}>
                            <input
                              type="text"
                              value={draft.name}
                              onChange={e => setDraft(d => ({...d, name: e.target.value}))}
                              disabled={saving}
                              className="settings-input"
                              style={{height:28, fontSize:13, padding:'0 8px'}}
                              aria-invalid={!!editError}
                            />
                            {editError && (
                              <span style={{fontSize:11, color:'var(--warn)', lineHeight:1.2}}>
                                {editError}
                              </span>
                            )}
                          </div>
                        ) : (
                          <span>
                            {s.name || s.email.split('@')[0]}
                            {isSelf && <span style={{marginLeft:8, fontSize:10, color:'var(--muted)', fontFamily:'var(--mono)'}}>(you)</span>}
                          </span>
                        )}
                      </div>
                    </td>
                    <td>
                      {isEditing ? (
                        <select
                          value={draft.role}
                          onChange={e => setDraft(d => ({...d, role: e.target.value}))}
                          disabled={saving}
                          className="settings-role-select"
                        >
                          {STAFF_ROLES.map(r => <option key={r} value={r}>{r}</option>)}
                        </select>
                      ) : (
                        <span className={'settings-role-pill' + (s.role === 'admin' ? ' admin' : '')}>
                          {s.role || 'staff'}
                        </span>
                      )}
                    </td>
                    <td className="mono">{s.email}</td>
                    <td className="mono">{fmtShortDay(s.created_at)}</td>
                    <td style={{textAlign:'right', paddingRight:16, whiteSpace:'nowrap'}}>
                      {isEditing ? (
                        <div style={{display:'inline-flex', gap:6}}>
                          <button type="button" className="btn sm" onClick={cancelEdit} disabled={saving}>Cancel</button>
                          <button
                            type="button"
                            className="btn sm primary"
                            onClick={() => saveEdit(s.id)}
                            disabled={saving || !draft.name.trim()}
                            title={!draft.name.trim() ? 'Name cannot be empty' : undefined}
                          >
                            {saving ? 'Saving…' : 'Save'}
                          </button>
                        </div>
                      ) : (
                        <div style={{display:'inline-flex', gap:6}}>
                          <button
                            type="button"
                            className="btn sm"
                            onClick={() => setResetTarget(s)}
                            title="Reset this user's password"
                          >Reset pw</button>
                          <button type="button" className="btn sm" onClick={() => startEdit(s)}>Edit</button>
                          {!isSelf && (
                            <button
                              type="button"
                              className="btn sm"
                              onClick={() => setRemoveTarget(s)}
                              style={{color:'var(--warn)'}}
                            >Remove</button>
                          )}
                        </div>
                      )}
                    </td>
                  </tr>
                </React.Fragment>
              );
            })}
          </tbody>
        </table>
      </div>
    </div>
  );
}

function IntegrationsPane({ user }) {
  const isAdmin = user?.role === 'admin';
  // Hard-gate the whole pane on admin. PR #21 already pinned require_admin
  // on /api/v2/admin/sync-* server-side, so a non-admin clicking the buttons
  // would 403 anyway — but rendering them and letting the request fail is
  // sloppy. Mirror the TeamPane gate pattern.
  const integ = useAsync(
    () => isAdmin ? api.get('/api/v2/system/integrations-status') : Promise.resolve({ integrations: [] }),
    [isAdmin]
  );
  const services = (integ.data?.integrations || []);
  const configured = services.filter(s => s.configured).length;
  const [syncing, setSyncing] = React.useState(null); // null | 'full' | 'recent'

  async function runSync(kind) {
    setSyncing(kind);
    try {
      const endpoint = kind === 'recent' ? '/api/v2/admin/sync-recent' : '/api/v2/admin/sync-contacts';
      await api.post(endpoint, {});
      alert(kind === 'recent' ? 'Recent sync (7d) started.' : 'Full sync started in background.');
    } catch (e) { alert(e.message || 'Sync failed'); }
    finally { setSyncing(null); }
  }

  if (!isAdmin) {
    return (
      <div className="settings-card">
        <div className="settings-card-label">Integrations</div>
        <div className="settings-empty">Admin only. Ask an admin to grant access.</div>
      </div>
    );
  }

  const jobs = [
    { name:'GHL incremental sync',   cadence:'every 15m',          detail:'sync_single_contact' },
    { name:'GHL full sweep',         cadence:'daily · 03:00 UTC',  detail:'_run_bulk_import' },
    { name:'Pending-approval expire',cadence:'every 60m',          detail:'auto_expire_cards' },
    { name:'Tommy worker',           cadence:'pg_notify',          detail:'tommy_new_message listener' },
  ];

  return (
    <>
      <div className="settings-card">
        <div className="settings-card-hd">
          <span className="settings-card-label">Integrations · {configured}/{services.length} configured</span>
          <div style={{display:'flex', gap:8}}>
            <button type="button" className="btn sm" onClick={() => runSync('recent')} disabled={!!syncing}>
              {syncing === 'recent' ? 'Syncing…' : 'Sync recent (7d)'}
            </button>
            <button type="button" className="btn sm" onClick={() => runSync('full')} disabled={!!syncing}>
              {syncing === 'full' ? 'Syncing…' : 'Full sync'}
            </button>
            <button type="button" className="btn sm" onClick={() => integ.reload()}>Refresh</button>
          </div>
        </div>
        <div className="settings-integ-list">
          {integ.loading && <div className="settings-empty">Loading…</div>}
          {!integ.loading && services.length === 0 && <div className="settings-empty">No integrations registered.</div>}
          {services.map(s => {
            const tone = s.coming_soon ? 'planned' : s.configured ? 'ok' : 'down';
            return (
              <div key={s.id || s.name} className="settings-integ-row">
                <span className={'settings-integ-dot ' + tone} />
                <div className="settings-integ-body">
                  <div className="settings-integ-name">{s.name}</div>
                  <div className="settings-integ-detail">{s.description}</div>
                </div>
                <div className="settings-integ-meta">
                  <div>{s.status_message}</div>
                  {s.last_activity_at && <div>last · {fmtAgoFrom(s.last_activity_at)}</div>}
                </div>
                <span className={'settings-status-pill ' + tone}>
                  {s.coming_soon ? 'Planned' : s.configured ? 'Healthy' : 'Needs setup'}
                </span>
              </div>
            );
          })}
        </div>
      </div>

      <div className="settings-card">
        <div className="settings-card-label">Background jobs</div>
        <div className="settings-jobs-list">
          {jobs.map(j => (
            <div key={j.name} className="settings-job-row">
              <span className="settings-job-name">{j.name}</span>
              <span className="settings-job-cadence">{j.cadence}</span>
              <span className="settings-job-detail">{j.detail}</span>
            </div>
          ))}
        </div>
      </div>
    </>
  );
}

function TommyAIPane() {
  const config = useAsync(() => api.get('/api/v2/system/tommy-config'), []);
  const cfg = config.data || {};
  return (
    <>
      <div className="settings-card">
        <div className="settings-card-label">Configuration</div>
        <div className="settings-cfg-grid">
          <div>
            <div className="settings-field-label">Model</div>
            <div className="settings-field-value settings-mono">{cfg.model || '—'}</div>
          </div>
          <div>
            <div className="settings-field-label">Memory</div>
            <div className="settings-field-value settings-mono">{cfg.memory_source || '—'}</div>
          </div>
          <div>
            <div className="settings-field-label">Tone</div>
            <div className="settings-field-value settings-mono">{cfg.tone || '—'}</div>
          </div>
          <div>
            <div className="settings-field-label">Contact routing</div>
            <div className="settings-field-value settings-mono">{cfg.contact_routing || '—'}</div>
          </div>
        </div>

        <div className="settings-field" style={{marginTop:14}}>
          <div className="settings-field-label">Approval triggers</div>
          <ul className="settings-triggers">
            {(cfg.approval_triggers || []).map((t, i) => <li key={i}>{t}</li>)}
            {(!cfg.approval_triggers || cfg.approval_triggers.length === 0) && (
              <li className="settings-muted">None configured</li>
            )}
          </ul>
        </div>
      </div>

      <div className="settings-card">
        <div className="settings-card-label">Force approval</div>
        <div className="settings-danger-row">
          <div>
            <div className="settings-danger-title">
              {cfg.force_approval ? 'Enabled' : 'Disabled'}
            </div>
            <div className="settings-danger-sub">
              Server flag <span className="settings-mono-inline">TOMMY_FORCE_APPROVAL</span>.
              When enabled, every Tommy draft waits for staff approval. Change via env var + redeploy.
            </div>
          </div>
          <span className={'settings-status-pill ' + (cfg.force_approval ? 'ok' : 'down')}>
            {cfg.force_approval ? 'Active' : 'Off'}
          </span>
        </div>
      </div>
    </>
  );
}

function NotificationsPane() {
  const prefs = useAsync(() => api.get('/api/v2/notifications/prefs'), []);
  const [local, setLocal] = React.useState({ tommy_approval: true, new_lead: false, daily_digest: true });
  React.useEffect(() => { if (prefs.data) setLocal(prefs.data); }, [prefs.data]);

  async function toggle(key) {
    const next = { ...local, [key]: !local[key] };
    setLocal(next);
    try { await api.patch('/api/v2/notifications/prefs', { [key]: next[key] }); }
    catch (e) { alert(e.message || 'Save failed'); setLocal(local); }
  }

  const rows = [
    { key: 'tommy_approval', label: 'Tommy approval', desc: 'A draft needs your approval' },
    { key: 'new_lead',       label: 'New lead',       desc: 'A contact is created in GHL' },
    { key: 'daily_digest',   label: 'Daily digest',   desc: 'One summary email each morning' },
  ];

  return (
    <div className="settings-card">
      <div className="settings-card-label">Notifications</div>
      {rows.map(r => (
        <div key={r.key} className="settings-pref-row">
          <div>
            <div className="settings-pref-label">{r.label}</div>
            <div className="settings-pref-desc">{r.desc}</div>
          </div>
          <SettingsToggle on={!!local[r.key]} onClick={() => toggle(r.key)} />
        </div>
      ))}
      <div className="settings-pref-foot">
        Stored per-user in <span className="settings-mono-inline">notification_prefs</span>.
      </div>
    </div>
  );
}

function SettingsView({ user, onSignOut }) {
  const isAdmin = user?.role === 'admin';
  // Only show the tabs the user can actually use. Team + Integrations are
  // admin-only — the panes themselves render an "Admin only" empty state if
  // a non-admin gets here through deep-linking, but pre-filtering keeps the
  // sidebar from being a tease.
  const allTabs = [
    { id: 'account',       label: 'Account',       ico: '◉', admin: false },
    { id: 'team',          label: 'Team',          ico: '◍', admin: true  },
    { id: 'integrations',  label: 'Integrations',  ico: '◈', admin: true  },
    { id: 'tommy',         label: 'Tommy AI',      ico: '◩', admin: false },
    { id: 'notifications', label: 'Notifications', ico: '⌖', admin: false },
  ];
  const tabs = allTabs.filter(t => !t.admin || isAdmin);
  const [tab, setTab] = React.useState('account');
  const navRef = React.useRef(null);

  // If a non-admin somehow lands on a hidden tab (stale localStorage, etc),
  // bounce them back to Account.
  React.useEffect(() => {
    if (!tabs.some(t => t.id === tab)) setTab('account');
  }, [tabs, tab]);

  // On phones the .settings-nav is a horizontal scroller; tabs past the
  // viewport edge are tappable but visually clipped (the user sees "TOM…").
  // When the active tab changes, scroll it into view so the selection
  // is always anchored regardless of which end of the strip it sits at.
  React.useEffect(() => {
    if (!navRef.current) return;
    const active = navRef.current.querySelector('.settings-tab.active');
    if (active && typeof active.scrollIntoView === 'function') {
      active.scrollIntoView({ inline: 'center', block: 'nearest', behavior: 'smooth' });
    }
  }, [tab]);

  return (
    <div className="app-main">
      <div className="page-hd">
        <div>
          <h1>Settings</h1>
          <div className="sub">Dashboard configuration, team, and Tommy AI</div>
        </div>
      </div>

      <div className="settings-grid">
        <nav ref={navRef} className="settings-nav" aria-label="Settings sections">
          {tabs.map(t => (
            <button
              key={t.id}
              type="button"
              className={'settings-tab' + (tab === t.id ? ' active' : '')}
              onClick={() => setTab(t.id)}
            >
              <span className="settings-tab-ico">{t.ico}</span>
              <span>{t.label}</span>
            </button>
          ))}
        </nav>

        <div className="settings-content">
          {tab === 'account'       && <AccountPane user={user} onSignOut={onSignOut} />}
          {tab === 'team'          && <TeamPane user={user} />}
          {tab === 'integrations'  && <IntegrationsPane user={user} />}
          {tab === 'tommy'         && <TommyAIPane />}
          {tab === 'notifications' && <NotificationsPane />}
        </div>
      </div>
    </div>
  );
}

// ---- System health ------------------------------------------------

Object.assign(window, { SettingsView });
