/* =============================================================================
   SheetHead — authentication, profile and Trophy UI
   Presentation only. Identity comes from the auth service (src/auth.js -> Supabase Auth); the Trophy balance and every Trophy change
   are READ from the database (written only by the trusted game server). Nothing here can create, edit or apply a Trophy delta.
   Exposes window.SH.ui.{AuthScreen, RecoveryScreen, AuthErrorScreen, UnconfiguredScreen, TrophyChip, TrophyResult, ProfileScreen, useTrophyResult}.
============================================================================= */
(function () {
  const { useState, useEffect, useRef, useMemo } = React;
  const PR = window.SH.presenter;
  const { cx, Icon, Button, Logo, Rule, Pip, Frame } = window.SH.ui;

  // ---- small helpers -------------------------------------------------------------------------------------------------------
  const fmt = (n) => (n == null ? '' : Number(n).toLocaleString('en-US'));
  function useCountTo(target, rm) {
    const [v, setV] = useState(target), from = useRef(target);
    useEffect(() => {
      if (target == null) return undefined;
      if (rm || from.current === target) { setV(target); from.current = target; return undefined; }
      const a = from.current, b = target, t0 = performance.now(); let raf = 0;
      const step = (t) => { const k = Math.min(1, (t - t0) / 900), e = 1 - Math.pow(1 - k, 3); setV(Math.round(a + (b - a) * e)); if (k < 1) raf = requestAnimationFrame(step); else from.current = b; };
      raf = requestAnimationFrame(step); return () => cancelAnimationFrame(raf);
    }, [target, rm]);
    return v;
  }
  const reduced = (settings) => !!(settings && (settings.reducedMotion || settings.animations === false));

  // ---- Trophy chip (navigation) ----------------------------------------------------------------------------------------------
  function TrophyChip({ user, settings }) {
    const shown = useCountTo(user && user.trophies, reduced(settings));
    const [pulse, setPulse] = useState(null), last = useRef(user && user.trophies);
    useEffect(() => {
      if (!user || user.trophies == null) return undefined;
      if (last.current != null && user.trophies !== last.current) { setPulse(user.trophies > last.current ? 'up' : 'down'); const t = setTimeout(() => setPulse(null), 1600); last.current = user.trophies; return () => clearTimeout(t); }
      last.current = user.trophies; return undefined;
    }, [user && user.trophies]);
    if (!user || user.trophies == null) return null;
    return (
      <span className={cx('trophy-chip', pulse)} data-testid="trophy-chip" data-trophies={user.trophies} title="Your Trophies" aria-label={fmt(user.trophies) + ' trophies'}>
        <span className="tc-cup" aria-hidden="true">{'🏆'}</span><b>{fmt(shown)}</b>
      </span>
    );
  }

  // ---- Authoritative Trophy result after a game -------------------------------------------------------------------------------
  // Polls the append-only ledger for this game (the trusted server writes it) and then refreshes the nav balance.
  function useTrophyResult(auth, gameId, enabled) {
    const [r, setR] = useState({ status: enabled && gameId ? 'pending' : 'none', row: null });
    useEffect(() => {
      if (!enabled || !gameId) { setR({ status: 'none', row: null }); return undefined; }
      let live = true, tries = 0, timer = null;
      const go = async () => {
        const row = await auth.trophyResult(gameId).catch(() => null);
        if (!live) return;
        if (row) { setR({ status: 'found', row }); auth.refreshProfile(); return; }
        if (++tries >= 10) { setR({ status: 'none', row: null }); return; }
        timer = setTimeout(go, 700);
      };
      go(); return () => { live = false; clearTimeout(timer); };
    }, [gameId, enabled]);
    return r;
  }
  const REASON = {
    NORMAL_GAME_WIN: ['Winner', 'win'], NORMAL_GAME_SECOND: ['2nd place', 'mid'], NORMAL_GAME_THIRD: ['3rd place', 'mid'], NORMAL_GAME_SHEETHEAD: ['SHEETHEAD', 'sheet'],
    TOURNAMENT_WIN: ['Tournament winner', 'win'], TOURNAMENT_SECOND: ['Tournament 2nd', 'mid'], TOURNAMENT_THIRD: ['Tournament 3rd', 'mid'], TOURNAMENT_LAST: ['Tournament last place', 'sheet'], ADMIN_ADJUSTMENT: ['Adjustment', 'mid']
  };
  // Displays the delta the DATABASE calculated. It never computes or applies anything.
  function TrophyResult({ row, settings }) {
    const [label, tone] = REASON[row.reason] || [row.reason, 'mid'];
    const rm = reduced(settings), shown = useCountTo(row.delta, rm);
    const floored = row.requested_delta < row.delta ? false : row.requested_delta !== row.delta;
    return (
      <div className={cx('trophy-result', tone, rm && 'rm')} data-testid="trophy-result" data-delta={row.delta} data-reason={row.reason} role="status" aria-live="polite">
        {tone === 'sheet' && <div className="tr-poop" aria-hidden="true">{'💩'}</div>}
        <div className="tr-label">{tone === 'sheet' ? 'SHEETHEAD' : label}</div>
        <div className="tr-delta"><span aria-hidden="true">{'🏆'}</span><b data-testid="trophy-delta">{row.delta > 0 ? '+' : row.delta < 0 ? '−' : '±'}{Math.abs(rm ? row.delta : shown)}</b></div>
        <div className="tr-bal">{fmt(row.previous_balance)} <i aria-hidden="true">{'→'}</i> <b>{fmt(row.new_balance)}</b></div>
        {floored && <div className="tr-note">Your Trophies cannot drop below 0.</div>}
        {tone === 'win' && <div className="tr-spark" aria-hidden="true"><i /><i /><i /><i /><i /><i /></div>}
      </div>
    );
  }

  // ---- shared form bits ---------------------------------------------------------------------------------------------------------
  function Field({ id, label, type, value, onChange, error, autoComplete, hint, disabled, inputMode, right }) {
    const [show, setShow] = useState(false), isPw = type === 'password';
    return (
      <div className={cx('field', error && 'bad')}>
        <label htmlFor={id}>{label}</label>
        <div className="field-box">
          <input id={id} name={id} type={isPw && show ? 'text' : type || 'text'} value={value} onChange={(e) => onChange(e.target.value)} autoComplete={autoComplete} inputMode={inputMode}
                 disabled={disabled} aria-invalid={error ? 'true' : undefined} aria-describedby={error ? id + '-err' : hint ? id + '-hint' : undefined} spellCheck={false} autoCapitalize="none" />
          {isPw && <button type="button" className="field-eye" onClick={() => setShow(s => !s)} aria-label={show ? 'Hide password' : 'Show password'} tabIndex={-1}>{show ? 'Hide' : 'Show'}</button>}
          {right}
        </div>
        {error ? <p id={id + '-err'} className="field-err" role="alert">{error}</p> : hint ? <p id={id + '-hint'} className="field-hint">{hint}</p> : null}
      </div>
    );
  }
  const Spinner = () => <span className="spin" aria-hidden="true" />;
  const Mono = ({ id }) => (id === 'google'
    ? <svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="#EA4335" d="M12 10.2v3.9h5.5c-.2 1.3-1.6 3.9-5.5 3.9-3.3 0-6-2.7-6-6.1s2.7-6.1 6-6.1c1.9 0 3.1.8 3.8 1.5l2.6-2.5C16.7 3.3 14.6 2.4 12 2.4 6.9 2.4 2.8 6.5 2.8 11.9S6.9 21.4 12 21.4c5.3 0 8.8-3.7 8.8-9 0-.6-.1-1.1-.2-1.6z" /></svg>
    : <svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="#1877F2" d="M24 12a12 12 0 1 0-13.9 11.9v-8.4H7.1V12h3V9.4c0-3 1.8-4.7 4.5-4.7 1.3 0 2.7.2 2.7.2v3h-1.5c-1.5 0-2 .9-2 1.9V12h3.4l-.5 3.5h-2.9v8.4A12 12 0 0 0 24 12z" /></svg>);

  // ---- Sign up / Log in ------------------------------------------------------------------------------------------------------
  // Small footer with the public legal pages (static HTML pages, not app screens).
  // `paper` is the variant for light card surfaces (the default suits the dark auth/landing background). Links open in the same tab.
  function LegalLinks({ paper, testId = 'legal-links' }) {
    return (
      <nav className={cx('legal-links', paper && 'on-paper')} aria-label="Legal" data-testid={testId}>
        <a href="/privacy">Privacy Policy</a><i aria-hidden="true" />
        <a href="/terms">Terms of Service</a><i aria-hidden="true" />
        <a href="/data-deletion">Data Deletion</a>
      </nav>
    );
  }

  function AuthScreen({ auth, authState, allowGuest, onGuest }) {
    const [mode, setMode] = useState('choose');            // choose | signup | login | forgot | confirm | resetSent
    const [f, setF] = useState({ email: '', password: '', username: '' });
    const [busy, setBusy] = useState(null);                 // 'signup' | 'login' | 'forgot' | 'google' | 'facebook'
    const [errs, setErrs] = useState({}), [formErr, setFormErr] = useState(null);
    const set = (k) => (v) => { setF(x => ({ ...x, [k]: v })); setErrs(e => ({ ...e, [k]: undefined })); setFormErr(null); };
    const go = (m) => { setMode(m); setErrs({}); setFormErr(null); auth.clearNotice(); };
    const providers = authState.providers || { google: true, facebook: true };

    const run = async (kind, fn, after) => {
      setBusy(kind); setErrs({}); setFormErr(null);
      const r = await fn(); setBusy(null);
      if (r.fieldErrors) setErrs(r.fieldErrors); else if (r.error) setFormErr(r.error); else if (after) after(r);
    };
    const oauth = (p) => run(p, () => auth.signInWithProvider(p));
    const submitSignUp = (e) => { e.preventDefault(); run('signup', () => auth.signUp(f), (r) => { if (r.needsConfirmation) go('confirm'); }); };
    const submitLogin = (e) => { e.preventDefault(); run('login', () => auth.signIn(f)); };
    const submitForgot = (e) => { e.preventDefault(); run('forgot', () => auth.requestPasswordReset(f.email), () => setMode('resetSent')); };
    const back = mode === 'choose' ? null : () => go(mode === 'confirm' || mode === 'resetSent' ? 'login' : 'choose');

    return (
      <div className="auth" data-testid="auth-screen" data-mode={mode}>
        {back && <button type="button" className="back back-float" onClick={back} aria-label="Back"><Icon name="back" size={16} /><span>Back</span></button>}
        <section className="ticket auth-ticket" aria-labelledby="auth-h">
          <Logo variant="lockup" size={40} className="ticket-logo" />
          {authState.notice && (
            <p className="auth-notice" role="status" data-testid="auth-notice">
              {authState.notice}
              {/link/i.test(authState.notice) && mode !== 'forgot' && <> <button type="button" className="linkish" data-testid="notice-new-link" onClick={() => go('forgot')}>Send me a new link</button></>}
            </p>
          )}

          {mode === 'choose' && (
            <>
              <Rule label="Reserved seat" />
              <h1 id="auth-h" className="ticket-title">Take a seat</h1>
              <p className="ticket-sub">Empty your hand. Dodge the poop hat. Sign in to keep your Trophies and play with others.</p>
              <div className="auth-list">
                {[['google', 'Google'], ['facebook', 'Facebook']].map(([id, name]) => (
                  <button key={id} type="button" className="provider" data-testid={'provider-' + id} onClick={() => oauth(id)} disabled={!!busy || providers[id] === false}
                          title={providers[id] === false ? 'Not available yet' : undefined}>
                    <span className="pv-mono" aria-hidden="true"><Mono id={id} /></span>
                    <span className="pv-name">{busy === id ? 'Opening ' + name + '…' : 'Continue with ' + name}{providers[id] === false && ' (not available yet)'}</span>
                    {busy === id ? <Spinner /> : <Pip suit="S" size={14} className="pv-pip" />}
                  </button>
                ))}
              </div>
              {formErr && <p className="form-err" role="alert" data-testid="auth-error">{formErr}</p>}
              <div className="auth-or"><i /><span>or</span><i /></div>
              <Button variant="primary" onClick={() => go('signup')} data-testid="create-account" icon={<Icon name="play" size={16} />}>Create a SheetHead Account</Button>
              <p className="auth-switch">Already have an account? <button type="button" className="linkish" data-testid="go-login" onClick={() => go('login')}>Log In</button></p>
              {allowGuest && <p className="auth-guest"><button type="button" className="linkish" data-testid="guest" onClick={onGuest}>Practice offline against bots (no account)</button></p>}
            </>
          )}

          {mode === 'signup' && (
            <form onSubmit={submitSignUp} noValidate data-testid="signup-form">
              <Rule label="New player" />
              <h1 id="auth-h" className="ticket-title sm">Create your account</h1>
              <Field id="su-username" label="Username" value={f.username} onChange={set('username')} error={errs.username} autoComplete="username" hint="3-20 letters, numbers or underscores. This is how other players find you." disabled={!!busy} />
              <Field id="su-email" label="Email" type="email" value={f.email} onChange={set('email')} error={errs.email} autoComplete="email" inputMode="email" disabled={!!busy} />
              <Field id="su-password" label="Password" type="password" value={f.password} onChange={set('password')} error={errs.password} autoComplete="new-password" hint="At least 8 characters, with upper- and lowercase letters and a number." disabled={!!busy} />
              {formErr && <p className="form-err" role="alert" data-testid="auth-error">{formErr}</p>}
              <Button variant="primary" type="submit" disabled={!!busy} data-testid="signup-submit">{busy === 'signup' ? <><Spinner /> Creating…</> : 'Create account'}</Button>
              <p className="auth-switch">Have an account? <button type="button" className="linkish" onClick={() => go('login')}>Log In</button></p>
            </form>
          )}

          {mode === 'login' && (
            <form onSubmit={submitLogin} noValidate data-testid="login-form">
              <Rule label="Welcome back" />
              <h1 id="auth-h" className="ticket-title sm">Log in</h1>
              <Field id="li-email" label="Email" type="email" value={f.email} onChange={set('email')} error={errs.email} autoComplete="email" inputMode="email" disabled={!!busy} />
              <Field id="li-password" label="Password" type="password" value={f.password} onChange={set('password')} error={errs.password} autoComplete="current-password" disabled={!!busy} />
              {formErr && <p className="form-err" role="alert" data-testid="auth-error">{formErr}</p>}
              <Button variant="primary" type="submit" disabled={!!busy} data-testid="login-submit">{busy === 'login' ? <><Spinner /> Logging in…</> : 'Log In'}</Button>
              <p className="auth-switch"><button type="button" className="linkish" data-testid="go-forgot" onClick={() => go('forgot')}>Forgot your password?</button></p>
              <p className="auth-switch">New here? <button type="button" className="linkish" onClick={() => go('signup')}>Create a SheetHead Account</button></p>
            </form>
          )}

          {mode === 'forgot' && (
            <form onSubmit={submitForgot} noValidate data-testid="forgot-form">
              <Rule label="Reset" />
              <h1 id="auth-h" className="ticket-title sm">Forgot your password?</h1>
              <p className="ticket-sub">Enter your email and we will send you a link to choose a new one.</p>
              <Field id="fg-email" label="Email" type="email" value={f.email} onChange={set('email')} error={errs.email} autoComplete="email" inputMode="email" disabled={!!busy} />
              {formErr && <p className="form-err" role="alert" data-testid="auth-error">{formErr}</p>}
              <Button variant="primary" type="submit" disabled={!!busy} data-testid="forgot-submit">{busy === 'forgot' ? <><Spinner /> Sending…</> : 'Send reset link'}</Button>
            </form>
          )}

          {mode === 'confirm' && (
            <div className="auth-done" data-testid="confirm-sent">
              <Rule label="Almost there" />
              <h1 id="auth-h" className="ticket-title sm">Check your email</h1>
              <p className="ticket-sub">We sent a confirmation link to <b>{f.email}</b>. Open it to activate your account, then come back and log in.</p>
              <Button variant="primary" onClick={() => go('login')}>Go to Log In</Button>
            </div>
          )}
          {mode === 'resetSent' && (
            <div className="auth-done" data-testid="reset-sent">
              <Rule label="Sent" />
              <h1 id="auth-h" className="ticket-title sm">Check your email</h1>
              <p className="ticket-sub">If an account exists for <b>{f.email}</b>, a reset link is on its way. It works once and expires soon.</p>
              <Button variant="primary" onClick={() => go('login')}>Back to Log In</Button>
            </div>
          )}
        </section>
        <LegalLinks />
      </div>
    );
  }

  // The user followed a password-reset link: choose a new password.
  function RecoveryScreen({ auth }) {
    const [pw, setPw] = useState(''), [busy, setBusy] = useState(false), [err, setErr] = useState(null), [fe, setFe] = useState(null);
    const submit = async (e) => { e.preventDefault(); setBusy(true); setErr(null); setFe(null); const r = await auth.updatePassword(pw); setBusy(false); if (r.fieldErrors) setFe(r.fieldErrors.password); else if (r.error) setErr(r.error); };
    return (
      <div className="auth" data-testid="recovery-screen">
        <section className="ticket auth-ticket" aria-labelledby="rc-h">
          <Logo variant="lockup" size={40} className="ticket-logo" /><Rule label="New password" />
          <h1 id="rc-h" className="ticket-title sm">Choose a new password</h1>
          <form onSubmit={submit} noValidate>
            <Field id="rc-pw" label="New password" type="password" value={pw} onChange={setPw} error={fe} autoComplete="new-password" hint="At least 8 characters, with upper- and lowercase letters and a number." disabled={busy} />
            {err && <p className="form-err" role="alert" data-testid="auth-error">{err}</p>}
            <Button variant="primary" type="submit" disabled={busy} data-testid="recovery-submit">{busy ? <><Spinner /> Saving…</> : 'Save password'}</Button>
          </form>
        </section>
      </div>
    );
  }

  // The session or profile could not be loaded: never a blank screen.
  function AuthErrorScreen({ auth, authState }) {
    return (
      <div className="auth" data-testid="auth-error-screen">
        <section className="ticket auth-ticket" role="alert">
          <Logo variant="lockup" size={40} className="ticket-logo" /><Rule label="Trouble at the table" />
          <h1 className="ticket-title sm">We couldn't load your account</h1>
          <p className="ticket-sub">{authState.error}</p>
          <div className="cta-col"><Button variant="primary" onClick={() => auth.retry()} data-testid="auth-retry">Try again</Button>{!auth.sdkMissing && <Button variant="ghost-dark" onClick={() => auth.signOut()}>Sign out</Button>}</div>
        </section>
      </div>
    );
  }

  // No backend configured for this deployment: say so plainly (there is no fake login to fall back on).
  function UnconfiguredScreen({ allowGuest, onGuest, env }) {
    return (
      <div className="auth" data-testid="unconfigured-screen">
        <section className="ticket auth-ticket">
          <Logo variant="lockup" size={40} className="ticket-logo" /><Rule label="Setup needed" />
          <h1 className="ticket-title sm">Accounts aren't connected yet</h1>
          <p className="ticket-sub">This copy of SheetHead has no account backend configured, so sign-in is unavailable. Run <code>node scripts/build-config.mjs</code> with your Supabase URL and public key (see docs/ACCOUNTS.md).</p>
          {allowGuest && <div className="cta-col"><Button variant="ghost-dark" onClick={onGuest} data-testid="guest">Practice offline against bots</Button></div>}
        </section>
      </div>
    );
  }

  // ---- Profile -------------------------------------------------------------------------------------------------------------------
  const AVATAR_KEYS = ['eagle', 'fox', 'lion', 'shark', 'bear', 'owl', 'wolf', 'panda'];
  function ProfileScreen({ back, user, auth, setScreen, settings }) {
    const guest = user && user.provider === 'guest';
    const [name, setName] = useState(user ? user.name : ''), [avatar, setAvatar] = useState(user ? user.avatarKey : 'eagle'), [busy, setBusy] = useState(false), [msg, setMsg] = useState(null);
    const since = user && user.createdAt ? new Date(user.createdAt).toLocaleDateString('en-US', { month: 'long', year: 'numeric' }) : null;
    const dirty = user && (name.trim() !== user.name || avatar !== user.avatarKey);
    const save = async () => { setBusy(true); setMsg(null); const r = await auth.updateProfile({ displayName: name, avatarKey: avatar }); setBusy(false); setMsg(r.ok ? { ok: true, t: 'Saved.' } : { ok: false, t: r.error }); };
    const av = PR.avatarByKey(avatar);
    return (
      <Frame title="Player card" kicker="Profile" onBack={back} wide bodyClass="center">
        <div className="split split-profile" data-testid="profile-screen">
          <section className="idcard" aria-label="Player card">
            <span className="fc-idx tl"><b>{(name || 'P')[0].toUpperCase()}</b><Pip suit="S" size={16} /></span>
            <span className="fc-idx br"><b>{(name || 'P')[0].toUpperCase()}</b><Pip suit="S" size={16} /></span>
            <div className="id-avatar big" style={{ '--hue': av.hue }} aria-hidden="true">{av.glyph}</div>
            <div className="id-name" data-testid="profile-name">{user ? user.name : 'Player'}</div>
            {!guest && <div className="id-user">@{user.username}</div>}
            {!guest && <div className="id-trophies"><TrophyChip user={user} settings={settings} /></div>}
            <div className="id-meta">{guest ? 'Guest (not signed in)' : 'Signed in with ' + user.provider}{since ? ' · since ' + since : ''}</div>
          </section>
          <section className="ticket">
            <span className="kicker dark">{guest ? 'Guest' : 'Your account'}</span>
            {guest ? <p className="ticket-sub" style={{ marginTop: 12 }}>You are practicing offline. Sign in to keep your Trophies, statistics and friends.</p> : (
              <div className="profile-form">
                <Field id="pf-name" label="Display name" value={name} onChange={(v) => { setName(v); setMsg(null); }} disabled={busy} hint="Shown at the table. Your username (@handle) cannot be changed." />
                <div className="avatar-pick" role="radiogroup" aria-label="Avatar">
                  {AVATAR_KEYS.map(k => { const a = PR.avatarByKey(k); return <button key={k} type="button" role="radio" aria-checked={avatar === k} aria-label={k} className={cx('ap', avatar === k && 'on')} style={{ '--hue': a.hue }} onClick={() => { setAvatar(k); setMsg(null); }}>{a.glyph}</button>; })}
                </div>
                {msg && <p className={msg.ok ? 'form-ok' : 'form-err'} role="status" data-testid="profile-msg">{msg.t}</p>}
                <Button variant="primary" onClick={save} disabled={busy || !dirty} data-testid="profile-save">{busy ? <><Spinner /> Saving…</> : 'Save changes'}</Button>
              </div>
            )}
            <div className="cta-col">
              <Button variant="ghost-dark" onClick={() => setScreen('stats')}>Open statistics</Button>
              <Button variant="ghost-dark" onClick={() => auth.signOut()} data-testid="signout">{guest ? 'Leave guest mode' : 'Log out'}</Button>
            </div>
            <LegalLinks paper testId="legal-links-profile" />
          </section>
        </div>
      </Frame>
    );
  }

  Object.assign(window.SH.ui, { LegalLinks, AuthScreen, RecoveryScreen, AuthErrorScreen, UnconfiguredScreen, TrophyChip, TrophyResult, ProfileScreen, useTrophyResult, AVATAR_KEYS });
})();
