import { useEffect, useRef, useState } from 'react'
import { Routes, Route, Link, Navigate, useLocation, useNavigate, useParams, useSearchParams } from 'react-router-dom'
import { getMe, subscribeToNotifications } from './api'
import { anonymize, EVENTS, identify, track } from './lib/analytics'
import { useLastState } from './lib/useLastState'
import { brandTitle } from './lib/brand'
import { entryPath, proposalPath, DEFAULT_COLLECTION } from './lib/entryPaths'
import { useDeployment } from './context/DeploymentProvider'
import ProjectLayout from './components/ProjectLayout.jsx'
import NotFound from './components/NotFound.jsx'
import Directory from './components/Directory.jsx'
import ProjectSwitcher from './components/ProjectSwitcher.jsx'
import Catalog from './components/Catalog.jsx'
import Inbox from './components/Inbox.jsx'
import RFCView from './components/RFCView.jsx'
import PRView from './components/PRView.jsx'
import ProposalView from './components/ProposalView.jsx'
import ProposeModal from './components/ProposeModal.jsx'
import CollectionDirectory from './components/CollectionDirectory.jsx'
import ContributeRequestForm from './components/ContributeRequestForm.jsx'
import Landing from './components/Landing.jsx'
import Login from './components/Login.jsx'
import BetaPending from './components/BetaPending.jsx'
import Philosophy from './components/Philosophy.jsx'
import DocsLayout from './components/DocsLayout.jsx'
import DocsUserGuide from './components/DocsUserGuide.jsx'
import DocsSessionsAbout from './components/DocsSessionsAbout.jsx'
import DocsSessionIndex from './components/DocsSessionIndex.jsx'
import DocsSessionTranscript from './components/DocsSessionTranscript.jsx'
import DocsSpec from './components/DocsSpec.jsx'
import DocsSpecsIndex from './components/DocsSpecsIndex.jsx'
import NotificationSettings from './components/NotificationSettings.jsx'
import Admin from './components/Admin.jsx'
import AcceptInvitation from './components/AcceptInvitation.jsx'
import InviteClaim from './components/InviteClaim.jsx'
import ToastHost, { showToast } from './components/ToastHost.jsx'
import CookieConsentBanner from './components/CookieConsentBanner.jsx'
import Privacy from './pages/Privacy.jsx'
import Cookies from './pages/Cookies.jsx'
import './App.css'

export default function App() {
  const [me, setMe] = useState(null)
  const [loading, setLoading] = useState(true)
  const [proposeOpen, setProposeOpen] = useState(false)
  const [catalogVersion, setCatalogVersion] = useState(0)
  const [inboxOpen, setInboxOpen] = useState(false)
  const [unreadCount, setUnreadCount] = useState(0)
  const [inboxTick, setInboxTick] = useState(0)
  // §14.5: a tick that, when bumped, asks <CookieConsentBanner> to
  // re-open even if the user has already made a choice. The settings
  // "Privacy & cookies" tab dispatches a `rfc-app:cookie-consent-reopen`
  // event that bumps this.
  const [consentReopenTick, setConsentReopenTick] = useState(0)
  // v0.23.0 / item #29 — flips true once the #21-Part-C identify effect
  // has fired (or once we've confirmed there's no authenticated user to
  // identify). useLastState gates its resume redirect on this so the
  // redirect always happens AFTER identify, preserving identify-then-
  // track ordering.
  const [identifyReady, setIdentifyReady] = useState(false)
  const navigate = useNavigate()
  const location = useLocation()
  // §22.9 — runtime deployment config (name for the brand, default project id
  // for building corpus links this slice; see DeploymentProvider).
  const deployment = useDeployment()
  // §22.4 — the project the viewer is currently in (from the /p/<id>/ URL),
  // so the propose modal (App-level chrome, above the route tree) targets the
  // right project. Falls back to the deployment default off a project route.
  const _projMatch = location.pathname.match(/^\/p\/([^/]+)/)
  const currentProjectId = (_projMatch && _projMatch[1]) || deployment.defaultProjectId
  // §22 S2 — the collection the viewer is currently in (from the /c/<cid>/ URL
  // segment), so a propose targets that collection. Falls back to the default.
  const _colMatch = location.pathname.match(/^\/p\/[^/]+\/c\/([^/]+)/)
  const currentCollectionId = (_colMatch && _colMatch[1]) || DEFAULT_COLLECTION
  // #28 Parts 2–3: the LinkedText create/contribute affordances route via
  // query params so they need no prop-threading from deep in a comment
  // list. `?propose=<term>` opens the propose modal pre-filled;
  // `?contribute=<slug>&term=<term>` opens the contribute-request form.
  const [searchParams, setSearchParams] = useSearchParams()
  const proposeParam = searchParams.get('propose')
  const contributeSlug = searchParams.get('contribute')
  const contributeTerm = searchParams.get('term')
  const clearParams = (...keys) => {
    const next = new URLSearchParams(searchParams)
    keys.forEach(k => next.delete(k))
    setSearchParams(next, { replace: true })
  }
  // v0.15.0 — Page Viewed event taxonomy. We fire on every
  // route change; the analytics wrapper itself decides whether
  // anything ships out (consent + key check). The first fire is
  // also covered because `location` is set on mount.
  const lastPathRef = useRef(null)
  useEffect(() => {
    const path = location.pathname + (location.search || '')
    if (lastPathRef.current === path) return
    lastPathRef.current = path
    track(EVENTS.PAGE_VIEWED, { path: location.pathname })
  }, [location.pathname, location.search])

  // §22.9 — tab title from runtime config. On deployment-chrome routes the
  // title is the deployment name; under a `/p/<project>/` route ProjectLayout
  // owns it (the project name), so skip those here.
  useEffect(() => {
    if (deployment.loading) return
    if (!location.pathname.startsWith('/p/')) {
      document.title = brandTitle(deployment.name)
    }
  }, [location.pathname, deployment.loading, deployment.name])

  // v0.15.0 + #21 Part C — bind the authenticated user id AND
  // durable user properties to the analytics session when sign-in
  // lands; reset on sign-out (viewer flips to null). The wrapper
  // queues these calls until consent + init resolve, so the order
  // is safe even on a cold load.
  //
  // Property bag passed to identify (set vs setOnce per #21 Part C):
  //   set:     role, permission_state, passcode_set, device_trusted
  //            (these can change mid-account-life — refresh each sign-in)
  //   setOnce: first_sign_in_at, account_created_at
  //            (immutable user-history markers — set on the first
  //            sign-in that observes them, never overwritten)
  //
  // PII discipline: NO email, NO display_name, NO gitea_login passed
  // through — Amplitude only sees opaque ids + enums + timestamps +
  // booleans.
  const lastUserIdRef = useRef(null)
  useEffect(() => {
    const uid = me?.authenticated ? me.user?.id : null
    const viewer = me?.authenticated ? me.user : null
    if (uid != null && lastUserIdRef.current !== uid) {
      lastUserIdRef.current = uid
      const props = {}
      if (viewer?.role != null) props.role = viewer.role
      if (viewer?.permission_state != null) props.permission_state = viewer.permission_state
      if (viewer?.passcode_set != null) props.passcode_set = !!viewer.passcode_set
      if (viewer?.device_trusted != null) props.device_trusted = !!viewer.device_trusted
      if (viewer?.first_sign_in_at) props.first_sign_in_at = ['__setOnce__', viewer.first_sign_in_at]
      if (viewer?.created_at) props.account_created_at = ['__setOnce__', viewer.created_at]
      identify({ user_id: String(uid), properties: props })
      // v0.23.0 / item #29 — identify has now fired for this sign-in;
      // release useLastState's resume redirect (it waits on this).
      setIdentifyReady(true)
    } else if (uid == null && lastUserIdRef.current != null) {
      // Sign-out edge — App-level reset is handled separately by the
      // sign-out gesture that fires User Signed Out. Clear our local
      // memo so a fresh sign-in re-fires identify.
      lastUserIdRef.current = null
    }
  }, [me?.authenticated, me?.user?.id, me?.user?.role, me?.user?.permission_state, me?.user?.passcode_set, me?.user?.device_trusted])

  // v0.23.0 / item #29 — once `me` has resolved, if there's no
  // authenticated user there is nothing to identify, so release the
  // resume gate immediately (anonymous boots have no resume to do, but
  // the hook still needs the gate resolved to be a clean no-op).
  useEffect(() => {
    if (me != null && !me.authenticated) setIdentifyReady(true)
  }, [me])

  useEffect(() => {
    const handler = () => setConsentReopenTick(t => t + 1)
    window.addEventListener('rfc-app:cookie-consent-reopen', handler)
    return () => window.removeEventListener('rfc-app:cookie-consent-reopen', handler)
  }, [])

  useEffect(() => {
    getMe()
      .then(setMe)
      .catch(() => setMe({ authenticated: false }))
      .finally(() => setLoading(false))
  }, [])

  // v0.23.0 / item #29 — server-side sign-in state resume. The hook
  // debounce-posts the current route for authenticated users and, once
  // identify has fired, redirects a fresh sign-in (which hard-lands on
  // "/") to the user's stored last route. Anonymous users: no-op.
  useLastState({
    authenticated: !!me?.authenticated,
    pathname: location.pathname,
    identifyReady,
    lastRoute: me?.authenticated ? me.user?.last_route : null,
    navigate,
  })

  // §15.3 — subscribe to the live SSE stream for authenticated viewers
  // so the badge counter and the toast surface stay in lockstep with
  // the inbox. Tabs that miss an event because they were closed pick
  // it up on next sign-in via the snapshot frame.
  useEffect(() => {
    if (!me?.authenticated) return undefined
    const close = subscribeToNotifications({
      onSnapshot: payload => setUnreadCount(payload.unread_count || 0),
      onNotification: payload => {
        setUnreadCount(c => c + 1)
        setInboxTick(t => t + 1)
        // §15.3: personal-direct events get a toast even when the user
        // isn't on the relevant view — they're the named subject.
        // Churn never toasts; structural toasts only when it lands on
        // a slug the user is currently viewing (URL match).
        const isPersonal = payload.category === 'personal-direct'
        // §22.10: entry URLs are now /p/<project>/e/<slug>; match + link on the
        // generic /e/<slug> segment (default project is the served corpus).
        const onCurrentSlug = payload.rfc_slug && window.location.pathname.includes(`/e/${payload.rfc_slug}`)
        if (isPersonal || onCurrentSlug) {
          showToast({
            summary: payload.summary,
            category: payload.category,
            link: payload.rfc_slug && deployment.defaultProjectId
              ? entryPath(deployment.defaultProjectId, payload.rfc_slug)
              : null,
          })
        }
      },
      onRead: () => {
        setUnreadCount(c => Math.max(0, c - 1))
        setInboxTick(t => t + 1)
      },
    })
    return close
  }, [me?.authenticated, deployment.defaultProjectId])

  if (loading) {
    return <div className="boot">Loading…</div>
  }

  // The deployment is in private beta: anonymous visitors get the full
  // app in read-only mode (viewer = null is passed through to every
  // component), and write affordances are hidden at the component
  // level. v0.8.0 (§6.1 / item #6): authenticated users with
  // `permission_state='pending'` also pass through as `viewer` with
  // their state attached — every write-gated affordance reads the
  // state and treats pending the same as anonymous, while reads
  // remain open. The /beta-pending page is the home root for a
  // pending user.
  const viewer = me?.authenticated ? me.user : null
  const isAdmin = viewer && (viewer.role === 'owner' || viewer.role === 'admin')
  const isPending = viewer && viewer.permission_state === 'pending'

  return (
    <div className="app">
      <header className="app-header">
        <div className="app-brand">
          {/* §22.9: deployment name from runtime config (replaces the
              build-time VITE_APP_NAME); neutral 'RFC' during the pre-fetch
              paint via brandTitle(). */}
          <Link to="/">{brandTitle(deployment.name)}</Link>
          {/* §22.10: project switcher — deployment chrome; renders only when
              2+ projects are visible to the caller. */}
          <ProjectSwitcher />
        </div>
        <div className="header-right">
          {/* §14.3: the persistent About link. One word, no badge, no
              state — visible from every screen so a viewer mid-PR who
              wonders why a conversation is public can reach the answer
              in two clicks. Anonymous viewers see it too. */}
          <Link to="/philosophy" className="header-about" title="Why this exists (§14)">
            About
          </Link>
          <Link to="/docs" className="header-about" title="User guide">
            Docs
          </Link>
          {viewer && (
            <Link to="/settings/notifications" className="header-settings" title="Notification settings (§15)">
              Settings
            </Link>
          )}
          {isAdmin && (
            <Link to="/admin" className="header-admin" title="Admin home base">
              Admin
            </Link>
          )}
          {viewer && (
            <button
              className="inbox-trigger"
              onClick={() => setInboxOpen(o => !o)}
              aria-label="Inbox"
              title="Inbox (§15.2)"
            >
              <svg
                width="18" height="18" viewBox="0 0 24 24"
                fill="none" stroke="currentColor" strokeWidth="1.75"
                strokeLinecap="round" strokeLinejoin="round" aria-hidden
              >
                <path d="M4 5h16a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1Z" />
                <path d="m3.5 6.5 8.5 6 8.5-6" />
              </svg>
              {unreadCount > 0 && (
                <span className="badge">{unreadCount > 99 ? '99+' : unreadCount}</span>
              )}
            </button>
          )}
          {viewer ? (
            <>
              <span className="user-name">{viewer.display_name}</span>
              <span className={`user-role-badge role-${viewer.role}`}>{viewer.role}</span>
              <a
                className="btn-link"
                href="/auth/logout"
                onClick={() => {
                  // v0.15.0 — fire the sign-out event before the
                  // hard nav. The wrapper's track() is sync-enqueue;
                  // the underlying SDK flush is best-effort across
                  // navigation. anonymize() clears the user binding
                  // so any post-nav anonymous events on the next
                  // page aren't attributed to the prior user.
                  track(EVENTS.USER_SIGNED_OUT)
                  anonymize()
                }}
              >Sign out</a>
            </>
          ) : (
            <Link className="btn-signin-header" to="/login" title="Private beta — only invited emails can sign in">
              Sign in <span className="beta-chip">Beta</span>
            </Link>
          )}
        </div>
      </header>
      {isPending && <PendingAccessBanner />}
      <div className="app-body">
        <Routes>
          <Route path="/welcome" element={<Landing />} />
          <Route path="/login" element={<Login />} />
          <Route path="/beta-pending" element={<BetaPending viewer={viewer} />} />
          {/* v0.16.0 (item #12): per-RFC invitation acceptance landing.
              Anonymous viewers see a sign-in prompt; signed-in users
              see the preview + accept gesture. */}
          <Route path="/invitations/accept" element={
            <PolicyShell><AcceptInvitation viewer={viewer} /></PolicyShell>
          } />
          {/* v0.17.0 — roadmap item #16. The claim landing page for
              admin-issued invites. Anonymous-reachable; the call
              itself establishes the session on success. */}
          <Route path="/invites/claim" element={<InviteClaim />} />
          <Route path="/philosophy" element={<PhilosophyWithSidebar viewer={viewer} />} />
          {/* v0.19.0 / roadmap item #30 — /docs/* is a hub with sub-nav.
              The bare /docs path redirects to the user guide; sessions
              browser lives at /docs/sessions/*. See DocsLayout.jsx
              for the flyout shape and CHANGELOG v0.19.0 for the
              upgrade path. */}
          <Route path="/docs" element={<Navigate to="/docs/user-guide" replace />} />
          <Route path="/docs/*" element={<DocsWithSidebar viewer={viewer} />} />
          {/* §14.5 / §14.6: cookie-consent companions to /philosophy.
              Available to anonymous and authenticated viewers alike. */}
          <Route path="/privacy" element={<PolicyShell><Privacy /></PolicyShell>} />
          <Route path="/cookies" element={<PolicyShell><Cookies /></PolicyShell>} />
          {viewer && (
            <Route path="/settings/notifications" element={<NotificationSettingsWithSidebar viewer={viewer} />} />
          )}
          {isAdmin && (
            <Route path="/admin/*" element={<AdminWithSidebar viewer={viewer} />} />
          )}
          {/* §22.10: the deployment landing at `/` — redirect into the single
              visible project (N=1, OHM's "land in the corpus" UX) or show the
              directory when 2+ are visible. */}
          <Route path="/" element={<DeploymentLanding />} />
          {/* §22.10: per-project subtree. ProjectLayout fetches the project,
              applies its theme, provides ProjectContext, and guards the corpus
              (served only for the corpus-served default; others get a
              placeholder). The generic `/e/` segment carries every entry type;
              the noun ("RFC"/"Spec"/"Feature") is a type-driven label. */}
          <Route path="/p/:projectId/*" element={
            <ProjectLayout>
              <Catalog
                viewer={viewer}
                onProposeRFC={() => setProposeOpen(true)}
                version={catalogVersion}
              />
              <main className="main-pane">
                <Routes>
                  {/* §22 S2: the project landing is the collection directory —
                      it lists collections, or (C3.7/C3.8) redirects into the
                      sole visible collection when there is exactly one. */}
                  <Route path="" element={<CollectionDirectoryRoute />} />
                  {/* Backcompat: the shipped v0.35.0 corpus URLs without a
                      /c/<collection>/ segment redirect into the default
                      collection, so old bookmarks keep working. */}
                  <Route path="e/:slug" element={<LegacyCorpusRedirect kind="entry" />} />
                  <Route path="e/:slug/pr/:prNumber" element={<LegacyCorpusRedirect kind="entryPr" />} />
                  <Route path="proposals/:prNumber" element={<LegacyCorpusRedirect kind="proposal" />} />
                  {/* Collection-scoped corpus. Serving stays project-scoped in
                      S1 (collection = default); collection-aware serving is S2. */}
                  <Route path="c/:collectionId" element={<Welcome viewer={viewer} />} />
                  <Route path="c/:collectionId/e/:slug" element={<RFCView viewer={viewer} />} />
                  <Route path="c/:collectionId/e/:slug/pr/:prNumber" element={<PRView viewer={viewer} />} />
                  <Route path="c/:collectionId/proposals/:prNumber" element={<ProposalView viewer={viewer} onChange={() => setCatalogVersion(v => v + 1)} />} />
                  <Route path="*" element={<NotFound />} />
                </Routes>
              </main>
            </ProjectLayout>
          } />
          {/* Any unmatched path is a real 404 — surface it rather than
              silently bouncing home, so a bad/typo'd URL is visible. */}
          <Route path="*" element={<NotFound />} />
        </Routes>
      </div>
      {(proposeOpen || proposeParam != null) && viewer && (
        <ProposeModal
          viewer={viewer}
          initialTitle={proposeParam || ''}
          projectId={currentProjectId}
          collectionId={currentCollectionId}
          onClose={() => { setProposeOpen(false); clearParams('propose') }}
          onSubmitted={({ pr_number }) => {
            setProposeOpen(false)
            clearParams('propose')
            setCatalogVersion(v => v + 1)
            navigate(proposalPath(currentProjectId, pr_number, currentCollectionId))
          }}
        />
      )}
      {contributeSlug && viewer && (
        <ContributeRequestForm
          slug={contributeSlug}
          term={contributeTerm || ''}
          onClose={() => clearParams('contribute', 'term')}
        />
      )}
      {inboxOpen && viewer && (
        <Inbox onClose={() => setInboxOpen(false)} lastChangeTick={inboxTick} />
      )}
      <ToastHost />
      <CookieConsentBanner viewer={viewer} forceOpen={consentReopenTick} />
    </div>
  )
}

// §22 S2 — the project landing at /p/:projectId/ is the collection directory.
// A tiny wrapper reads the route's projectId and hands it to CollectionDirectory
// (which lists collections, or redirects into the sole one — C3.7/C3.8).
function CollectionDirectoryRoute() {
  const { projectId } = useParams()
  return <CollectionDirectory projectId={projectId} />
}

// Backcompat for the shipped v0.35.0 corpus URLs that lacked the
// /c/<collection>/ segment: redirect into the default collection, preserving any
// query string (e.g. ?branch=).
function LegacyCorpusRedirect({ kind }) {
  const { projectId, slug, prNumber } = useParams()
  const { search } = useLocation()
  const base = `/p/${projectId}/c/${DEFAULT_COLLECTION}`
  let to = `${base}/`
  if (kind === 'entry') to = `${base}/e/${slug}`
  else if (kind === 'entryPr') to = `${base}/e/${slug}/pr/${prNumber}`
  else if (kind === 'proposal') to = `${base}/proposals/${prNumber}`
  return <Navigate to={to + (search || '')} replace />
}

function DeploymentLanding() {
  // §22.10 + design decision 2 — N=1 lands in the single visible project so
  // OHM's "land in the corpus" UX is preserved; the directory appears only
  // when 2+ projects are visible to the caller. Visibility is per-caller
  // (§22.5), so the unlisted projects never count toward the directory.
  const { projects, defaultProjectId, defaultProjectReadable, loading } = useDeployment()
  if (loading) {
    return <main className="chrome-pane"><div className="boot">Loading…</div></main>
  }
  if (projects.length === 1) {
    return <Navigate to={`/p/${projects[0].id}/`} replace />
  }
  if (projects.length === 0 && defaultProjectReadable && defaultProjectId) {
    // No enumerable projects but the default is readable by this viewer (e.g. an
    // anon hitting a deployment whose only project is unlisted-but-default) —
    // land there. §22 S5: when the default is gated to this viewer (C3.2) or
    // absent (C3.1, no projects), we do NOT bounce into a 404 — we fall through
    // to the directory's role-aware empty state below.
    return <Navigate to={`/p/${defaultProjectId}/`} replace />
  }
  return <Directory />
}

function PolicyShell({ children }) {
  // §14.5 / §14.6 policy pages reuse the chrome-pane shape so they
  // render full-width without the catalog rail. The components inside
  // carry their own back affordance per Philosophy.jsx's pattern.
  return <main className="chrome-pane">{children}</main>
}

function PhilosophyWithSidebar({ viewer }) {
  // The chrome surfaces (§14.2 philosophy, §15 settings, §6/§17 admin)
  // all use the full app body — no catalog left pane, no propose modal.
  // The header carries the navigation back; the body is a single
  // reading surface.
  return (
    <main className="chrome-pane">
      <Philosophy authenticated={!!viewer} />
    </main>
  )
}

function DocsWithSidebar({ viewer }) {
  // v0.19.0 / roadmap item #30 — the `/docs/*` surface is a flyout
  // shell with sub-routes. The shell (sidebar + content area) is the
  // DocsLayout outlet host; the sub-routes mount their respective
  // pages into the outlet. Bare `/docs/sessions` redirects to the
  // sessions about page so deep-linkers and the flyout's "Sessions"
  // header both land somewhere coherent.
  return (
    <main className="chrome-pane">
      <Routes>
        <Route element={<DocsLayout authenticated={!!viewer} />}>
          <Route index element={<Navigate to="user-guide" replace />} />
          <Route path="user-guide" element={<DocsUserGuide />} />
          <Route path="sessions" element={<Navigate to="about" replace />} />
          <Route path="sessions/about" element={<DocsSessionsAbout />} />
          <Route path="sessions/:nnnn" element={<DocsSessionIndex />} />
          <Route path="sessions/:nnnn/:filename" element={<DocsSessionTranscript />} />
          {/* v0.20.0 — /docs/specs/* surface (framework spec + flotilla spec
              at runtime via gitea raw). Bare /docs/specs lands on the
              client-side redirect to the first configured spec. */}
          <Route path="specs" element={<DocsSpecsIndex />} />
          <Route path="specs/:name" element={<DocsSpec />} />
          <Route path="*" element={<NotFound />} />
        </Route>
      </Routes>
    </main>
  )
}

function NotificationSettingsWithSidebar({ viewer }) {
  return (
    <main className="chrome-pane">
      <NotificationSettings viewer={viewer} />
    </main>
  )
}

function AdminWithSidebar({ viewer }) {
  return (
    <main className="chrome-pane">
      <Admin viewer={viewer} />
    </main>
  )
}

function PendingAccessBanner() {
  // v0.8.0 — thin banner shown on every page (other than /beta-pending
  // itself, which carries the same message in larger form) when the
  // signed-in user's `permission_state='pending'`. Sign-out works
  // normally via the header affordance.
  return (
    <div className="pending-access-banner">
      Your beta access request is in review.{' '}
      <Link to="/beta-pending">Learn more →</Link>
    </div>
  )
}

function Welcome({ viewer }) {
  // v0.8.0 — a pending user landing on "/" gets the same page they'd
  // see at /beta-pending, inline. This is the post-OTC home root for
  // a user awaiting admin grant.
  if (viewer && viewer.permission_state === 'pending') {
    return <BetaPending viewer={viewer} />
  }
  if (!viewer) {
    return (
      <div className="welcome">
        <h1>Welcome.</h1>
        <p>
          The catalog on the left lists every super-draft and active RFC in the
          framework. Open one to read the canonical body and the public
          conversation behind each definition.
        </p>
        <p>
          Discussion and contribution are in private <strong>Beta</strong> —
          read freely, and <Link to="/login">sign in</Link> if your email has
          been invited.
        </p>
        <p>
          Wondering why a conversation is public, why graduation costs what it
          does, or why the model is in the chat? <Link to="/philosophy">Read the
          philosophy</Link>.
        </p>
      </div>
    )
  }
  return (
    <div className="welcome">
      <h1>Welcome, {viewer.display_name}.</h1>
      <p>
        The catalog on the left lists every super-draft and active RFC in the
        framework. Open one to read the canonical body, or use{' '}
        <strong>Propose New RFC</strong> at the bottom of the catalog to open an
        idea PR against the meta repository.
      </p>
      <p>
        Wondering why a conversation is public, why graduation costs what it
        does, or why the model is in the chat? <Link to="/philosophy">Read the
        philosophy</Link>.
      </p>
    </div>
  )
}
