// Authentication: login screen, user menu, role-based permissions

// --- Permissions ---------------------------------------------------------
// Returns capability flags for a given profile.
// profile = { role, team, ... } | null
function permissionsFor(profile) {
  const role = profile?.role || null;
  const team = profile?.team || null;
  const isAdmin = role === 'admin';
  const isStrategy = role === 'strategy';
  const isTeamMember = role === 'team_member';
  const isAoxMember = isTeamMember && team === 'aox';
  const isViewer = role === 'viewer' || role === null || role === undefined;

  const canEdit = isAdmin || isStrategy || isAoxMember;
  const canCreate = isAdmin || isStrategy || isAoxMember;
  const canDelete = isAdmin;

  return {
    role: role || 'viewer',
    team,
    canEdit, canCreate, canDelete,
    isAdmin, isStrategy, isTeamMember, isAoxMember, isViewer,
    label: role || 'viewer',
  };
}

// Role badge color tokens
const ROLE_COLORS = {
  admin:       { bg:'rgba(255,59,48,0.10)',  fg:'#FF3B30', border:'rgba(255,59,48,0.30)'  },
  strategy:    { bg:'rgba(88,86,214,0.12)',  fg:'#5856D6', border:'rgba(88,86,214,0.30)'  },
  team_member: { bg:'rgba(0,122,255,0.10)',  fg:'#007AFF', border:'rgba(0,122,255,0.30)'  },
  viewer:      { bg:'rgba(142,142,147,0.14)',fg:'#8E8E93', border:'rgba(142,142,147,0.32)'},
};

function RoleBadge({ role, team, lang, compact }) {
  const c = ROLE_COLORS[role] || ROLE_COLORS.viewer;
  const labels = lang === 'en'
    ? { admin:'Admin', strategy:'Strategy', team_member: team ? `Team · ${team.toUpperCase()}` : 'Team', viewer:'Viewer' }
    : { admin:'관리자', strategy:'전략', team_member: team ? `팀원 · ${team.toUpperCase()}` : '팀원', viewer:'뷰어' };
  return (
    <span style={{
      display:'inline-flex', alignItems:'center', gap: 4,
      padding: compact ? '1px 6px' : '2px 8px',
      borderRadius: 999,
      background: c.bg, color: c.fg,
      border: `1px solid ${c.border}`,
      fontSize: compact ? 10 : 11, fontWeight: 600,
      letterSpacing: -0.005, whiteSpace:'nowrap'
    }}>{labels[role] || role}</span>
  );
}

// --- Login Screen --------------------------------------------------------
// Invite-only. Supabase "Allow new users to sign up" is OFF — the UI used
// to have a Sign-up tab that only produced errors, so it's been removed.
// Accounts are provisioned from shared.team_roster by an admin.
function LoginScreen({ lang, onLangToggle }) {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [showPw, setShowPw] = useState(false);
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState('');
  const [emailErr, setEmailErr] = useState('');
  const [pwErr, setPwErr] = useState('');
  const [notice, setNotice] = useState(''); // success notice (reset sent)
  const [resetMode, setResetMode] = useState(false);

  function clearErrors(){ setError(''); setEmailErr(''); setPwErr(''); setNotice(''); }
  function validEmail(v){ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v); }

  const T = {
    ko: {
      title: 'ADRO Internal Platform',
      sub: 'AOX 사업화 WBS 관리 시스템',
      email: '이메일', password: '비밀번호',
      signinBtn: '로그인',
      signingIn: '로그인 중…',
      forgot: '비밀번호 찾기',
      inviteHint: '계정이 필요하면 관리자에게 초대를 요청하세요 (mean@adro.com).',
      invalidEmail: '올바른 이메일 주소를 입력해 주세요',
      required: '필수 항목입니다',
      invalidCreds: '이메일 또는 비밀번호가 올바르지 않습니다',
      notConfirmed: '이메일의 확인 링크를 먼저 클릭해주세요',
      rate: '요청이 너무 잦습니다. 잠시 후 다시 시도해 주세요',
      network: '네트워크 연결을 확인하고 다시 시도해 주세요',
      generic: '요청을 처리하지 못했습니다',
      resetTitle: '비밀번호 재설정',
      resetDesc: '가입하신 이메일로 재설정 링크를 보내드립니다.',
      resetBtn: '재설정 링크 전송',
      resetSending: '전송 중…',
      resetSent: (v)=>`${v}로 재설정 링크를 보냈습니다. 메일함을 확인해 주세요.`,
      back: '로그인으로 돌아가기',
      showPw: '비밀번호 표시', hidePw: '비밀번호 숨기기',
      footer: '© ADRO · AOX_WBS',
      switchLang: '한국어',
    },
    en: {
      title: 'ADRO Internal Platform',
      sub: 'AOX Go-to-Market WBS',
      email: 'Email', password: 'Password',
      signinBtn: 'Sign in',
      signingIn: 'Signing in…',
      forgot: 'Forgot password?',
      inviteHint: 'Need an account? Ask an admin for an invite (mean@adro.com).',
      invalidEmail: 'Please enter a valid email address',
      required: 'Required',
      invalidCreds: 'Incorrect email or password',
      notConfirmed: 'Please click the confirmation link in your email first.',
      rate: 'Too many requests. Please try again shortly.',
      network: 'Network error — check your connection and retry.',
      generic: 'Something went wrong',
      resetTitle: 'Reset password',
      resetDesc: 'We’ll email a reset link to your account.',
      resetBtn: 'Send reset link',
      resetSending: 'Sending…',
      resetSent: (v)=>`Reset link sent to ${v}. Check your inbox.`,
      back: 'Back to sign in',
      showPw: 'Show password', hidePw: 'Hide password',
      footer: '© ADRO · AOX_WBS',
      switchLang: 'English',
    },
  };
  const t = T[lang] || T.ko;

  function mapError(code){
    switch(code){
      case 'INVALID_CREDENTIALS': return t.invalidCreds;
      case 'EMAIL_NOT_CONFIRMED': return t.notConfirmed;
      case 'INVALID_EMAIL':       return t.invalidEmail;
      case 'RATE_LIMIT':          return t.rate;
      case 'NETWORK':             return t.network;
      default: return t.generic;
    }
  }

  async function doSignIn(){
    clearErrors();
    const v = email.trim();
    let bad = false;
    if (!validEmail(v)) { setEmailErr(t.invalidEmail); bad = true; }
    if (!password) { setPwErr(t.required); bad = true; }
    if (bad) return;
    setBusy(true);
    try {
      await window.AOX_DB.signIn(v, password);
      // onAuthStateChange in app.jsx will flip to the app
    } catch (e) {
      const code = e?.code || '';
      if (code === 'INVALID_EMAIL') setEmailErr(t.invalidEmail);
      else if (code === 'INVALID_CREDENTIALS') setError(t.invalidCreds);
      else setError(mapError(code));
    } finally { setBusy(false); }
  }

  async function doReset(){
    clearErrors();
    const v = email.trim();
    if (!validEmail(v)) { setEmailErr(t.invalidEmail); return; }
    setBusy(true);
    try {
      await window.AOX_DB.resetPassword(v);
      setNotice(t.resetSent(v));
    } catch (e) {
      setError(mapError(e?.code));
    } finally { setBusy(false); }
  }

  // --- input style helpers
  const inputStyle = (hasErr) => ({
    width:'100%', padding:'11px 13px',
    background:'var(--bg-solid)',
    border: '1px solid ' + (hasErr ? 'var(--risk-high)' : 'var(--border)'),
    borderRadius: 10, fontSize: 14, color:'var(--fg)',
    outline:'none', transition:'border-color .15s, box-shadow .15s',
    fontFamily:'var(--font-sans)',
  });
  const labelStyle = {display:'block', fontSize: 11.5, fontWeight: 600, color:'var(--fg-muted)', marginBottom: 6, letterSpacing: 0.02};
  const errLine = (m) => m ? <div style={{marginTop: 6, fontSize: 11.5, color:'var(--risk-high)'}}>{m}</div> : null;

  const primaryBtnStyle = {
    width:'100%', marginTop: 14, padding:'12px 16px',
    background:'var(--accent)', border:'none',
    borderRadius: 10,
    display:'flex', alignItems:'center', justifyContent:'center', gap: 8,
    fontSize: 14, fontWeight: 600, color:'#fff',
    cursor: busy ? 'wait' : 'pointer',
    boxShadow: '0 4px 14px color-mix(in srgb, var(--accent) 32%, transparent)',
  };

  // Eye-toggle SVG (reused by both password fields)
  const eyeSvg = showPw ? (
    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/>
      <line x1="1" y1="1" x2="23" y2="23"/>
    </svg>
  ) : (
    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
      <circle cx="12" cy="12" r="3"/>
    </svg>
  );

  const eyeBtnStyle = {
    position:'absolute', right: 8, top:'50%', transform:'translateY(-50%)',
    width: 28, height: 28, padding: 0,
    background:'transparent', border:'none', cursor:'pointer',
    color:'var(--fg-muted)', display:'flex', alignItems:'center', justifyContent:'center',
    borderRadius: 6,
  };


  return (
    <div style={{
      position:'fixed', inset:0,
      display:'flex', alignItems:'center', justifyContent:'center',
      background: 'var(--bg)',
      backgroundImage: `radial-gradient(at 20% 10%, var(--accent-soft) 0px, transparent 50%),
                        radial-gradient(at 85% 90%, color-mix(in srgb, var(--phase2) 18%, transparent) 0px, transparent 55%)`,
    }}>
      <div style={{position:'absolute', top: 18, right: 22}}>
        <button className="btn" onClick={onLangToggle} style={{fontSize: 11.5, padding:'4px 10px'}}>
          {lang === 'en' ? '한국어' : 'English'}
        </button>
      </div>

      <div style={{position:'absolute', top: 22, left: 24, display:'flex', alignItems:'center', gap: 8}}>
        <div style={{
          width: 28, height: 28, borderRadius: 7,
          background: 'var(--fg)', color:'var(--bg-solid)',
          display:'flex', alignItems:'center', justifyContent:'center',
          fontSize: 11, fontWeight: 800, letterSpacing: -0.02
        }}>A</div>
        <div style={{fontSize: 13, fontWeight: 600, color:'var(--fg)'}}>ADRO</div>
      </div>

      <div style={{
        width: 'min(420px, 92vw)',
        padding: '36px 32px 28px',
        background: 'var(--bg-solid)',
        border: '1px solid var(--border)',
        borderRadius: 16,
        boxShadow: 'var(--shadow-2)',
      }}>
        <div style={{
          display:'flex', justifyContent:'center', margin: '0 auto 18px',
        }}>
          <AoxLogo size="lg"/>
        </div>

        <h1 style={{
          textAlign:'center', margin: 0,
          fontSize: 20, fontWeight: 700, letterSpacing: -0.018,
          color:'var(--fg)'
        }}>{t.title}</h1>
        <p style={{
          textAlign:'center', margin: '6px 0 22px',
          fontSize: 13, color:'var(--fg-muted)', lineHeight: 1.5
        }}>{t.sub}</p>

        {resetMode ? (
          <>
            <div style={{fontSize: 14, fontWeight: 600, color:'var(--fg)', marginBottom: 6}}>{t.resetTitle}</div>
            <div style={{fontSize: 12, color:'var(--fg-muted)', marginBottom: 16, lineHeight: 1.5}}>{t.resetDesc}</div>

            <label style={labelStyle}>{t.email}</label>
            <input type="email" autoFocus autoComplete="email"
              value={email}
              onChange={e=>{ setEmail(e.target.value); if (emailErr) setEmailErr(''); }}
              onKeyDown={e=>{ if (e.key==='Enter' && !busy) doReset(); }}
              placeholder="your.email@adro.com" disabled={busy}
              style={inputStyle(!!emailErr)}
              onFocus={e=>{ if (!emailErr) e.currentTarget.style.borderColor='var(--accent)'; }}
              onBlur={e=>{ if (!emailErr) e.currentTarget.style.borderColor='var(--border)'; }}
            />
            {errLine(emailErr)}

            <button onClick={doReset} disabled={busy} style={primaryBtnStyle}>
              {busy ? t.resetSending : t.resetBtn}
            </button>

            {notice && (
              <div style={{
                marginTop: 14, padding:'10px 12px',
                background:'color-mix(in srgb, var(--accent) 10%, var(--bg-solid))',
                border:'1px solid color-mix(in srgb, var(--accent) 30%, var(--border))',
                color:'var(--fg)', borderRadius: 10, fontSize: 12.5, lineHeight: 1.5
              }}>{notice}</div>
            )}
            {error && (
              <div style={{
                marginTop: 14, padding:'8px 12px',
                background:'rgba(255,59,48,0.10)', border:'1px solid rgba(255,59,48,0.30)',
                color:'#FF3B30', borderRadius: 8, fontSize: 12
              }}>{error}</div>
            )}

            <div style={{marginTop: 20, textAlign:'center'}}>
              <button onClick={()=>{ setResetMode(false); clearErrors(); }}
                style={{background:'transparent', border:'none', fontSize: 12, color:'var(--accent)', cursor:'pointer', textDecoration:'underline', textUnderlineOffset: 3}}>
                ← {t.back}
              </button>
            </div>
          </>
        ) : (
          <>
            {/* Tabs */}
            <label style={labelStyle}>{t.email}</label>
            <input type="email" autoFocus autoComplete="email"
              value={email}
              onChange={e=>{ setEmail(e.target.value); if (emailErr) setEmailErr(''); if (notice) setNotice(''); }}
              onKeyDown={e=>{ if (e.key==='Enter' && !busy) doSignIn(); }}
              placeholder="your.email@adro.com" disabled={busy}
              style={inputStyle(!!emailErr)}
              onFocus={e=>{ if (!emailErr) e.currentTarget.style.borderColor='var(--accent)'; }}
              onBlur={e=>{ if (!emailErr) e.currentTarget.style.borderColor='var(--border)'; }}
            />
            {errLine(emailErr)}

            <div style={{height: 12}}/>
            <label style={labelStyle}>{t.password}</label>
            <div style={{position:'relative'}}>
              <input
                type={showPw ? 'text' : 'password'}
                autoComplete="current-password"
                value={password}
                onChange={e=>{ setPassword(e.target.value); if (pwErr) setPwErr(''); }}
                onKeyDown={e=>{ if (e.key==='Enter' && !busy) doSignIn(); }}
                placeholder="••••••••"
                disabled={busy}
                style={{...inputStyle(!!pwErr), paddingRight: 40}}
                onFocus={e=>{ if (!pwErr) e.currentTarget.style.borderColor='var(--accent)'; }}
                onBlur={e=>{ if (!pwErr) e.currentTarget.style.borderColor='var(--border)'; }}
              />
              <button type="button" onClick={()=>setShowPw(s=>!s)}
                title={showPw ? t.hidePw : t.showPw}
                aria-label={showPw ? t.hidePw : t.showPw}
                style={eyeBtnStyle}
                onMouseEnter={e=>{ e.currentTarget.style.background='var(--hover)'; }}
                onMouseLeave={e=>{ e.currentTarget.style.background='transparent'; }}>
                {eyeSvg}
              </button>
            </div>
            {errLine(pwErr)}

            <button onClick={doSignIn} disabled={busy} style={primaryBtnStyle}>
              {busy ? t.signingIn : t.signinBtn}
            </button>

            <div style={{marginTop: 14, textAlign:'center', fontSize: 12, color:'var(--fg-muted)', lineHeight: 1.55}}>
              {t.inviteHint}
            </div>

            {notice && (
              <div style={{
                marginTop: 14, padding:'10px 12px',
                background:'color-mix(in srgb, var(--accent) 10%, var(--bg-solid))',
                border:'1px solid color-mix(in srgb, var(--accent) 30%, var(--border))',
                color:'var(--fg)', borderRadius: 10, fontSize: 12.5, lineHeight: 1.5
              }}>{notice}</div>
            )}
            {error && (
              <div style={{
                marginTop: 14, padding:'8px 12px',
                background:'rgba(255,59,48,0.10)', border:'1px solid rgba(255,59,48,0.30)',
                color:'#FF3B30', borderRadius: 8, fontSize: 12
              }}>{error}</div>
            )}

            <div style={{marginTop: 18, textAlign:'center'}}>
              <button onClick={()=>{ setResetMode(true); clearErrors(); }}
                style={{background:'transparent', border:'none', fontSize: 12, color:'var(--fg-muted)', cursor:'pointer', textDecoration:'underline', textUnderlineOffset: 3}}>
                {t.forgot}
              </button>
            </div>
          </>
        )}
      </div>

      <div style={{
        position:'absolute', bottom: 18, left: 0, right: 0,
        textAlign:'center', fontSize: 10.5, color:'var(--fg-subtle)',
        letterSpacing: 0.04
      }}>{t.footer}</div>
    </div>
  );
}

// --- Change Password Modal -----------------------------------------------
function ChangePasswordModal({ user, lang, onClose, onSuccess }) {
  const [currentPw, setCurrentPw]   = useState('');
  const [newPw,     setNewPw]       = useState('');
  const [confirmPw, setConfirmPw]   = useState('');
  const [error,     setError]       = useState('');
  const [busy,      setBusy]        = useState(false);

  const T = {
    ko: {
      title: '비밀번호 변경',
      currentPw: '현재 비밀번호', newPw: '새 비밀번호', confirmPw: '새 비밀번호 확인',
      cancel: '취소', submit: '변경', submitting: '변경 중…',
      success: '비밀번호가 변경되었습니다',
      errRequired: '모든 항목을 입력해 주세요',
      errCurrentWrong: '현재 비밀번호가 일치하지 않습니다',
      errSamePw: '현재와 다른 비밀번호를 입력해 주세요',
      errTooShort: '새 비밀번호는 8자 이상이어야 합니다',
      errMismatch: '새 비밀번호가 일치하지 않습니다',
      errGeneric: '변경에 실패했습니다. 다시 시도해 주세요',
    },
    en: {
      title: 'Change Password',
      currentPw: 'Current Password', newPw: 'New Password', confirmPw: 'Confirm New Password',
      cancel: 'Cancel', submit: 'Change', submitting: 'Changing…',
      success: 'Password changed successfully',
      errRequired: 'Please fill in all fields',
      errCurrentWrong: 'Current password is incorrect',
      errSamePw: 'New password must differ from current password',
      errTooShort: 'New password must be at least 8 characters',
      errMismatch: 'Passwords do not match',
      errGeneric: 'Failed to change password. Please try again.',
    },
  };
  const t = T[lang] || T.ko;

  const inputStyle = { width:'100%', padding:'10px 12px', background:'var(--surface)', border:'1px solid var(--border)', borderRadius: 9, fontSize: 13.5, color:'var(--fg)', outline:'none', fontFamily:'var(--font-sans)', boxSizing:'border-box' };
  const labelStyle = { display:'block', fontSize: 11.5, fontWeight: 600, color:'var(--fg-muted)', marginBottom: 5 };

  async function handleSubmit(){
    setError('');
    if (!currentPw || !newPw || !confirmPw) { setError(t.errRequired); return; }
    if (newPw.length < 8)       { setError(t.errTooShort);     return; }
    if (newPw !== confirmPw)    { setError(t.errMismatch);     return; }
    if (newPw === currentPw)    { setError(t.errSamePw);       return; }
    setBusy(true);
    try {
      await window.AOX_DB.changePassword(user.email, currentPw, newPw);
      onSuccess(t.success);
      onClose();
    } catch (e) {
      setError(e.code === 'INVALID_CURRENT_PASSWORD' ? t.errCurrentWrong : t.errGeneric);
    } finally { setBusy(false); }
  }

  // Portal to document.body so .topbar's backdrop-filter doesn't create a
  // new fixed-positioning containing block and push the modal off-screen.
  return ReactDOM.createPortal(
    <div className="modal-backdrop" onClick={e=>{ if (e.target===e.currentTarget) onClose(); }}>
      <div className="modal" style={{width:'min(400px, 92vw)'}} onClick={e=>e.stopPropagation()}>
        <header>
          <h2>{t.title}</h2>
          <button onClick={onClose} style={{background:'transparent', border:'none', cursor:'pointer', color:'var(--fg-muted)', padding:4, borderRadius:6, lineHeight:0}}><Icon name="x" size={16}/></button>
        </header>

        <div className="body" style={{display:'flex', flexDirection:'column', gap:12}}>
          {[
            [t.currentPw, currentPw, setCurrentPw, 'current-password'],
            [t.newPw,     newPw,     setNewPw,      'new-password'],
            [t.confirmPw, confirmPw, setConfirmPw,  'new-password'],
          ].map(([label, val, set, ac], i) => (
            <div className="field" key={i}>
              <label>{label}</label>
              <input type="password" autoComplete={ac} autoFocus={i===0}
                value={val} onChange={e=>{ set(e.target.value); setError(''); }}
                onKeyDown={e=>{ if (e.key==='Enter') handleSubmit(); }}
                disabled={busy}
              />
            </div>
          ))}

          {error && (
            <div style={{padding:'8px 12px', background:'rgba(255,59,48,0.10)', border:'1px solid rgba(255,59,48,0.30)', color:'#FF3B30', borderRadius:8, fontSize:12, lineHeight:1.5}}>
              {error}
            </div>
          )}

          <div style={{display:'flex', gap:8, justifyContent:'flex-end', marginTop:4}}>
            <button onClick={onClose} disabled={busy} style={{padding:'8px 16px', borderRadius:8, background:'transparent', border:'1px solid var(--border)', fontSize:13, color:'var(--fg)', cursor:'pointer'}}>
              {t.cancel}
            </button>
            <button onClick={handleSubmit} disabled={busy} style={{padding:'8px 18px', borderRadius:8, background:'var(--accent)', border:'none', fontSize:13, fontWeight:600, color:'#fff', cursor:busy?'wait':'pointer'}}>
              {busy ? t.submitting : t.submit}
            </button>
          </div>
        </div>
      </div>
    </div>,
    document.body
  );
}

// --- User menu (avatar + dropdown) --------------------------------------
function UserMenu({ user, profile, perms, lang, onSignOut, filterOwner, setFilterOwner }) {
  const [open, setOpen] = useState(false);
  const [changePwOpen, setChangePwOpen] = useState(false);
  const [toast, setToast] = useState(null);
  const ref = useRef(null);
  useEffect(() => {
    function onDoc(e){ if (open && ref.current && !ref.current.contains(e.target)) setOpen(false); }
    document.addEventListener('mousedown', onDoc);
    return () => document.removeEventListener('mousedown', onDoc);
  }, [open]);

  function showToast(msg){ setToast(msg); setTimeout(()=>setToast(null), 3200); }

  const avatarUrl = user?.user_metadata?.avatar_url || profile?.avatar_url;
  const email = user?.email || profile?.email || '—';
  const name = profile?.full_name || user?.user_metadata?.full_name || email.split('@')[0];
  const initial = (name || email || '?').slice(0,1).toUpperCase();

  return (
    <div ref={ref} style={{position:'relative'}}>
      <button onClick={()=>setOpen(v=>!v)}
        title={email}
        style={{
          display:'flex', alignItems:'center', gap: 8,
          padding: '3px 10px 3px 3px',
          background: open ? 'var(--hover)' : 'transparent',
          border:'1px solid var(--border)',
          borderRadius: 999, cursor:'pointer',
        }}>
        {avatarUrl ? (
          <img src={avatarUrl} alt="" referrerPolicy="no-referrer"
            style={{width: 26, height: 26, borderRadius:'50%', objectFit:'cover'}}/>
        ) : (
          <span style={{
            width: 26, height: 26, borderRadius:'50%',
            background:'var(--accent)', color:'#fff',
            display:'flex', alignItems:'center', justifyContent:'center',
            fontSize: 11, fontWeight: 700,
          }}>{initial}</span>
        )}
        <RoleBadge role={perms.role} team={perms.team} lang={lang} compact/>
      </button>

      {open && (
        <div style={{
          position:'absolute', top:'calc(100% + 6px)', right: 0,
          width: 280, padding: 12,
          background:'var(--bg-solid)', border:'1px solid var(--border)',
          borderRadius: 12, boxShadow:'var(--shadow-2)', zIndex: 50,
        }}>
          <div style={{display:'flex', gap: 10, alignItems:'center', marginBottom: 10}}>
            {avatarUrl ? (
              <img src={avatarUrl} alt="" referrerPolicy="no-referrer"
                style={{width: 40, height: 40, borderRadius:'50%', objectFit:'cover'}}/>
            ) : (
              <span style={{
                width: 40, height: 40, borderRadius:'50%',
                background:'var(--accent)', color:'#fff',
                display:'flex', alignItems:'center', justifyContent:'center',
                fontSize: 16, fontWeight: 700,
              }}>{initial}</span>
            )}
            <div style={{flex:1, minWidth: 0}}>
              <div style={{fontSize: 13, fontWeight: 600, whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis'}}>{name}</div>
              <div className="muted" style={{fontSize: 11.5, whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis'}}>{email}</div>
            </div>
          </div>

          <div style={{
            padding: 10, background:'var(--surface-2)', borderRadius: 8,
            display:'grid', gridTemplateColumns:'auto 1fr', gap:'6px 10px',
            fontSize: 11.5, marginBottom: 10
          }}>
            <span className="muted">{lang==='en'?'Role':'역할'}</span>
            <RoleBadge role={perms.role} team={perms.team} lang={lang} compact/>
            <span className="muted">{lang==='en'?'Team':'팀'}</span>
            <span className="mono" style={{fontSize: 11}}>{perms.team || '—'}</span>
            <span className="muted">{lang==='en'?'Edit':'편집'}</span>
            <span>{perms.canEdit ? '✓' : '—'}</span>
            <span className="muted">{lang==='en'?'Delete':'삭제'}</span>
            <span>{perms.canDelete ? '✓' : '—'}</span>
          </div>

          {setFilterOwner && (
            <button
              onClick={() => { setFilterOwner(filterOwner === 'me' ? '' : 'me'); setOpen(false); }}
              style={{
                width:'100%', marginBottom: 8, padding:'8px 10px', borderRadius: 8,
                background: filterOwner === 'me' ? 'var(--accent-soft)' : 'transparent',
                border: '1px solid ' + (filterOwner === 'me' ? 'transparent' : 'var(--border)'),
                fontSize: 12.5,
                color: filterOwner === 'me' ? 'var(--accent)' : 'var(--fg)',
                cursor:'pointer',
                display:'flex', alignItems:'center', justifyContent:'center', gap: 6,
              }}
              onMouseEnter={e=>{ if (filterOwner !== 'me') e.currentTarget.style.background='var(--hover)'; }}
              onMouseLeave={e=>{ e.currentTarget.style.background = filterOwner === 'me' ? 'var(--accent-soft)' : 'transparent'; }}>
              <Icon name="list" size={13}/>
              {lang === 'en' ? 'My Tasks' : '내 태스크'}
              {filterOwner === 'me' && <Icon name="check" size={12}/>}
            </button>
          )}
          <button onClick={()=>{ setOpen(false); setChangePwOpen(true); }}
            style={{
              width:'100%', marginBottom: 8, padding:'8px 10px', borderRadius: 8,
              background:'transparent', border:'1px solid var(--border)',
              fontSize: 12.5, color:'var(--fg)', cursor:'pointer',
              display:'flex', alignItems:'center', justifyContent:'center', gap: 6,
            }}
            onMouseEnter={e=>{ e.currentTarget.style.background='var(--hover)'; }}
            onMouseLeave={e=>{ e.currentTarget.style.background='transparent'; }}>
            <Icon name="settings" size={13}/>
            {lang === 'en' ? 'Change Password' : '비밀번호 변경'}
          </button>
          <button onClick={onSignOut}
            style={{
              width:'100%', padding:'8px 10px', borderRadius: 8,
              background:'transparent', border:'1px solid var(--border)',
              fontSize: 12.5, color:'var(--fg)', cursor:'pointer',
              display:'flex', alignItems:'center', justifyContent:'center', gap: 6,
            }}
            onMouseEnter={e=>{ e.currentTarget.style.background='var(--hover)'; }}
            onMouseLeave={e=>{ e.currentTarget.style.background='transparent'; }}>
            <Icon name="x" size={13}/>
            {lang === 'en' ? 'Sign out' : '로그아웃'}
          </button>
        </div>
      )}

      {changePwOpen && (
        <ChangePasswordModal
          user={user}
          lang={lang}
          onClose={()=>setChangePwOpen(false)}
          onSuccess={showToast}
        />
      )}

      {toast && ReactDOM.createPortal(
        <div style={{
          position:'fixed', bottom: 24, left:'50%', transform:'translateX(-50%)',
          zIndex: 300, padding:'10px 20px',
          background:'var(--fg)', color:'var(--bg-solid)',
          borderRadius: 10, fontSize: 13, fontWeight: 500,
          boxShadow:'var(--shadow-2)', whiteSpace:'nowrap',
          animation:'aox-toast-in 200ms ease',
        }}>
          {toast}
        </div>,
        document.body
      )}
    </div>
  );
}

// Auth context provider (use via window.AOX_AUTH_CTX)
window.AOX_AUTH = { permissionsFor, ROLE_COLORS };
window.LoginScreen = LoginScreen;
window.UserMenu = UserMenu;
window.RoleBadge = RoleBadge;
