// auth.jsx — Supabase Auth UI + session management
// Exposes window.PH365_AUTH { useSession, useUser, AuthGate, signOut, openAuthModal }
//
// When Supabase isn't configured, useSession returns a fake "guest" session so
// the prototype keeps working without a backend.
(function () {
  const { useState, useEffect, useCallback } = React;
  const supabase = window.PH365_SUPABASE;
  const COLORS   = window.PH365_UI.COLORS;
  const { Display, Body, Caption, Icon } = window.PH365_UI;

  // ── Hooks ─────────────────────────────────────────────────────────────
  function useSession() {
    const [session, setSession] = useState(null);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
      if (!supabase) {
        setSession({ user: { id: 'guest', email: 'guest@local', isGuest: true } });
        setLoading(false);
        return;
      }
      let active = true;
      supabase.auth.getSession().then(({ data }) => {
        if (active) { setSession(data.session); setLoading(false); }
      });
      const { data: sub } = supabase.auth.onAuthStateChange((_evt, s) => setSession(s));
      return () => { active = false; sub.subscription.unsubscribe(); };
    }, []);

    return { session, loading };
  }

  function useUser() {
    const { session } = useSession();
    return session?.user ?? null;
  }

  // Is this page load the tail end of a password-reset link? Two independent
  // signals, because neither one alone is dependable:
  //
  //   * window.PH365_RECOVERY — set by the bootstrap in Passholder365.html,
  //     which reads the URL fragment before the Supabase client parses and
  //     strips it. This is the one that fires on the normal implicit flow.
  //   * the PASSWORD_RECOVERY auth event — emitted once the client has
  //     finished parsing, which also covers the PKCE '?code=' variant. On its
  //     own it races us: the client may well have finished before this
  //     component ever mounted, and nothing replays the event to a late
  //     subscriber.
  //
  // Returns [recovering, clearRecovery].
  function useRecovery() {
    const [recovering, setRecovering] = useState(() => !!window.PH365_RECOVERY);

    useEffect(() => {
      if (!supabase) return;
      const { data: sub } = supabase.auth.onAuthStateChange((evt) => {
        if (evt === 'PASSWORD_RECOVERY') setRecovering(true);
      });
      return () => sub.subscription.unsubscribe();
    }, []);

    const clear = useCallback(() => {
      window.PH365_RECOVERY = false;
      setRecovering(false);
    }, []);

    return [recovering, clear];
  }

  // ── Sign-in / sign-up modal ───────────────────────────────────────────
  function AuthModal({ onClose, initialMode }) {
    // 'signup' by default — friendlier first-run. `initialMode` is how the
    // password-reset link forces us straight into 'update'.
    const [mode, setMode] = useState(initialMode || 'signup');
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');
    const [confirm, setConfirm] = useState('');
    const [name, setName]    = useState('');
    const [busy, setBusy] = useState(false);
    const [msg, setMsg] = useState(null);
    const [err, setErr] = useState(null);
    const [done, setDone] = useState(false);  // success state after signup

    if (!supabase) {
      return (
        <ModalShell onClose={onClose} title="Auth not configured">
          <Body color={COLORS.inkSoft} size={13}>
            This deployment doesn't have Supabase wired up yet. Set{' '}
            <code style={{ background: COLORS.paperDeep, padding: '1px 4px', borderRadius: 3 }}>SUPABASE_URL</code>{' '}and{' '}
            <code style={{ background: COLORS.paperDeep, padding: '1px 4px', borderRadius: 3 }}>SUPABASE_ANON_KEY</code> in
            Vercel env vars to enable accounts.
          </Body>
          <Body size={12} color={COLORS.inkSoft} style={{ marginTop: 12 }}>
            For now, your check-ins and ratings are stored locally in this browser only.
          </Body>
        </ModalShell>
      );
    }

    async function submit(e) {
      e?.preventDefault();
      setBusy(true); setErr(null); setMsg(null);
      try {
        if (mode === 'signin') {
          const { error } = await supabase.auth.signInWithPassword({ email, password });
          if (error) throw error;
          onClose();
        } else if (mode === 'signup') {
          const { data, error } = await supabase.auth.signUp({
            email, password,
            options: {
              data: { full_name: name || null },
              // Confirmation email must land back on this deployment, not the
              // project's default Site URL. Requires the origin to be in the
              // Supabase auth redirect allow-list.
              emailRedirectTo: window.location.origin,
            },
          });
          if (error) throw error;
          // If email confirmation is OFF, session is returned and we can close.
          // If it's ON (default for hosted Supabase), data.session is null and
          // we need to ask the user to check their email.
          if (data.session) {
            onClose();
          } else {
            setDone(true);
            setMsg(`Account created! Check ${email} to confirm.`);
          }
        } else if (mode === 'magic') {
          const { error } = await supabase.auth.signInWithOtp({
            email, options: { shouldCreateUser: true, emailRedirectTo: window.location.origin },
          });
          if (error) throw error;
          setDone(true);
          setMsg(`Magic link sent to ${email}.`);
        } else if (mode === 'reset') {
          const { error } = await supabase.auth.resetPasswordForEmail(email, {
            redirectTo: window.location.origin,
          });
          if (error) throw error;
          setDone(true);
          setMsg(`Password-reset email sent to ${email}.`);
        } else if (mode === 'update') {
          if (password !== confirm) throw new Error("Those two passwords don't match.");
          // The recovery link already signed us in — a short-lived session
          // whose only purpose is to authorise this one call.
          const { error } = await supabase.auth.updateUser({ password });
          if (error) throw error;
          setDone(true);
          setMsg('Password updated.');
        }
      } catch (e) {
        // An expired or already-used link leaves no session behind, so
        // updateUser fails with a message that means nothing to the person
        // reading it. Say what actually happened and where the way out is.
        const m = String(e.message || '');
        setErr(mode === 'update' && /session|not authenticated|missing/i.test(m)
          ? 'That reset link has expired or was already used. Send yourself a fresh one below.'
          : m);
      }
      finally { setBusy(false); }
    }

    if (done) {
      return (
        <ModalShell onClose={onClose} title={mode === 'update' ? 'All set' : 'Almost there'}>
          <div style={{ textAlign: 'center', padding: '8px 0 4px' }}>
            <div style={{
              width: 48, height: 48, margin: '0 auto 14px',
              borderRadius: '50%', border: `2px solid ${COLORS.moss}`,
              display: 'flex', alignItems: 'center', justifyContent: 'center',
              color: COLORS.moss,
            }}>
              <Icon name="check" size={26} color={COLORS.moss} strokeWidth={2.5} />
            </div>
            <Body size={14}>{msg}</Body>
            {mode === 'update' ? (
              <>
                <Body size={12} color={COLORS.inkSoft} style={{ marginTop: 14, lineHeight: 1.5 }}>
                  You're signed in with your new password. Use it next time.
                </Body>
                <button onClick={onClose} style={{
                  background: COLORS.teal, color: COLORS.paper, border: 'none', borderRadius: 10,
                  padding: '12px 16px', fontFamily: 'Manrope', fontSize: 14, fontWeight: 600,
                  cursor: 'pointer', marginTop: 16, width: '100%',
                }}>Continue</button>
              </>
            ) : (
              <Body size={12} color={COLORS.inkSoft} style={{ marginTop: 14, lineHeight: 1.5 }}>
                Open the email and click the link to finish.
                You can close this window — we'll pick up your session automatically.
              </Body>
            )}
          </div>
        </ModalShell>
      );
    }

    const titles = {
      signin: 'Sign in',
      signup: 'Create your account',
      magic:  'Magic link sign-in',
      reset:  'Reset password',
      update: 'Set a new password',
    };

    return (
      <ModalShell onClose={onClose} title={titles[mode]}>
        {mode === 'signup' && (
          <Body size={12} color={COLORS.inkSoft} style={{ marginBottom: 10 }}>
            Free, takes 10 seconds. We use email only to confirm your account and
            send the password-reset link if you ever need it.
          </Body>
        )}
        {mode === 'update' && (
          <Body size={12} color={COLORS.inkSoft} style={{ marginBottom: 10 }}>
            You followed a reset link. Pick a new password and you're back in.
          </Body>
        )}
        <form onSubmit={submit} style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
          {mode === 'signup' && (
            <Field label="Display name (optional)" type="text" value={name} onChange={setName}
                   placeholder="What should we call you?" autoComplete="name" />
          )}
          {mode !== 'update' && (
            <Field label="Email" type="email" value={email} onChange={setEmail}
                   autoFocus={mode !== 'signup'} required autoComplete="email" />
          )}
          {(mode === 'signin' || mode === 'signup' || mode === 'update') && (
            <Field label={mode === 'update' ? 'New password' : 'Password'}
                   type="password" value={password} onChange={setPassword}
                   autoFocus={mode === 'update'}
                   required minLength={6}
                   autoComplete={mode === 'signin' ? 'current-password' : 'new-password'}
                   placeholder={mode === 'signin' ? '' : 'At least 6 characters'} />
          )}
          {mode === 'update' && (
            <Field label="Confirm new password" type="password" value={confirm} onChange={setConfirm}
                   required minLength={6} autoComplete="new-password" />
          )}
          {err && <Body size={12} color={COLORS.clay}>{err}</Body>}
          {msg && !done && <Body size={12} color={COLORS.moss}>{msg}</Body>}
          <button type="submit" disabled={busy} style={{
            background: COLORS.teal, color: COLORS.paper, border: 'none', borderRadius: 10,
            padding: '12px 16px', fontFamily: 'Manrope', fontSize: 14, fontWeight: 600,
            cursor: busy ? 'wait' : 'pointer', marginTop: 4,
          }}>{busy ? 'Working…' : ({
            signin: 'Sign in',
            signup: 'Create account',
            magic:  'Send magic link',
            reset:  'Send reset link',
            update: 'Save new password',
          }[mode])}</button>
        </form>

        <div style={{
          display: 'flex', gap: 12, marginTop: 14, fontSize: 12,
          justifyContent: 'center', flexWrap: 'wrap',
        }}>
          {/* In 'update' mode the only sideways move that makes sense is
              asking for another link — offering "Sign in" to someone who is
              here precisely because they can't is how this looked broken. */}
          {mode === 'update' && <Switcher onClick={() => { setMode('reset'); setErr(null); setMsg(null); }}>Send a new reset link</Switcher>}
          {mode !== 'update' && mode !== 'signin' && <Switcher onClick={() => { setMode('signin'); setErr(null); setMsg(null); }}>Sign in</Switcher>}
          {mode !== 'update' && mode !== 'signup' && <Switcher onClick={() => { setMode('signup'); setErr(null); setMsg(null); }}>Create account</Switcher>}
          {mode !== 'update' && mode !== 'magic'  && <Switcher onClick={() => { setMode('magic');  setErr(null); setMsg(null); }}>Magic link</Switcher>}
          {mode === 'signin' && <Switcher onClick={() => { setMode('reset');  setErr(null); setMsg(null); }}>Forgot password?</Switcher>}
        </div>
      </ModalShell>
    );
  }

  function ModalShell({ title, onClose, children }) {
    return (
      <div onClick={onClose} style={{
        position: 'absolute', inset: 0, background: 'rgba(26,37,102,0.55)',
        zIndex: 300, display: 'flex', alignItems: 'center', justifyContent: 'center',
        animation: 'fadeIn 0.18s ease-out',
      }}>
        <div onClick={e => e.stopPropagation()} style={{
          background: COLORS.paper, border: `1px solid ${COLORS.paperEdge}`, borderRadius: 14,
          padding: '20px 22px 24px', width: 320, maxWidth: '85%', position: 'relative',
        }}>
          <button onClick={onClose} aria-label="Close" style={{
            position: 'absolute', top: 12, right: 12, background: 'transparent',
            border: 'none', cursor: 'pointer', padding: 4,
          }}><Icon name="close" size={16} color={COLORS.inkSoft} /></button>
          <Display size={22} style={{ marginBottom: 14 }}>{title}</Display>
          {children}
        </div>
      </div>
    );
  }

  function Field({ label, value, onChange, type = 'text', ...rest }) {
    return (
      <label style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
        <Caption>{label}</Caption>
        <input type={type} value={value} onChange={e => onChange(e.target.value)} {...rest} style={{
          padding: '10px 12px', borderRadius: 8, border: `1px solid ${COLORS.paperEdge}`,
          fontFamily: 'Manrope', fontSize: 14, background: COLORS.paper, color: COLORS.ink,
        }} />
      </label>
    );
  }

  function Switcher({ onClick, children }) {
    return (
      <button type="button" onClick={onClick} style={{
        background: 'transparent', border: 'none', color: COLORS.teal, fontFamily: 'Manrope',
        fontSize: 12, fontWeight: 600, cursor: 'pointer', textDecoration: 'underline',
      }}>{children}</button>
    );
  }

  // ── Sign-out ──────────────────────────────────────────────────────────
  async function signOut() {
    if (!supabase) return;
    await supabase.auth.signOut();
  }

  // ── AuthGate: open modal on demand ────────────────────────────────────
  // Usage: <AuthGate>{({ user, openAuth }) => ... }</AuthGate>
  function AuthGate({ children }) {
    const { session, loading } = useSession();
    const [open, setOpen] = useState(false);
    const [recovering, clearRecovery] = useRecovery();
    const openAuth = useCallback(() => setOpen(true), []);
    const close = useCallback(() => { setOpen(false); clearRecovery(); }, [clearRecovery]);

    // Allow any descendant (or any other module) to open the auth modal
    // by dispatching a 'ph365-open-auth' event on window.
    useEffect(() => {
      const onOpen = () => setOpen(true);
      window.addEventListener('ph365-open-auth', onOpen);
      return () => window.removeEventListener('ph365-open-auth', onOpen);
    }, []);

    if (loading) return null;

    return (
      <>
        {children({ user: session?.user ?? null, openAuth, signOut })}
        {/* The key remounts the modal if recovery is detected while it happens
            to be open already — initialMode is only read on first render. */}
        {(open || recovering) && (
          <AuthModal
            key={recovering ? 'recover' : 'auth'}
            initialMode={recovering ? 'update' : undefined}
            onClose={close}
          />
        )}
      </>
    );
  }

  window.PH365_AUTH = { useSession, useUser, AuthGate, signOut };
})();
