// Top-level component. Owns routing, the session lifecycle, the
// background catalog refresh, and the single mount point for the
// persistent NowPlayingBar. Every screen reads the data it needs off
// the props it gets here.
//
// Routing
// ───────
// Hash-based (#/home, #/submit, #/inbox …). The 'home' route was
// added in the Beachy-Lila migration as the new default for anyone
// who isn't a moderator/admin — they used to land on /submit
// directly, but with a real catalog to browse it makes more sense
// for the front door to be "Entdecken" instead.
//
// Bouncing
// ────────
// Each effect below the route initialisation routes the user away
// from screens they can't access (e.g. non-admin on /audit). The
// fallback chain is: requested → home → submit → me. If even /me
// fails, the user is logged out and Login renders instead.

// `decided` removed in May 2026 — the Verlauf list folded into Stats
// as a sub-tab. `blocks` removed shortly after — folded into the
// Künstler page so admins manage uploads + bans in one surface.
// Bookmarks pointing at the old routes still bounce to a valid
// destination via the redirect block in ApprovalApp.
const ROUTES = ['home', 'inbox', 'submit', 'playlists', 'reports', 'audit', 'stats', 'artists', 'me'];

// Parse `#/route` OR `#/route/arg` (e.g. #/artists/pX01abcd). The
// `arg` slot is used by the Künstler page to deep-link into a
// specific artist's detail view. Other routes ignore it.
function useRoute(initial = 'home') {
  const read = () => {
    const raw = (window.location.hash || '').replace(/^#\/?/, '');
    const slash = raw.indexOf('/');
    const head = slash === -1 ? raw : raw.slice(0, slash);
    const arg  = slash === -1 ? null : raw.slice(slash + 1) || null;
    return {
      route: ROUTES.includes(head) ? head : initial,
      arg,
    };
  };
  const [state, setState] = React.useState(read());
  React.useEffect(() => {
    const onHash = () => setState(read());
    window.addEventListener('hashchange', onHash);
    return () => window.removeEventListener('hashchange', onHash);
  }, []);
  // setRoute('artists')           → #/artists
  // setRoute('artists', 'pX01')   → #/artists/pX01
  const setRoute = (r, a) => {
    window.location.hash = '#/' + r + (a ? '/' + a : '');
  };
  return [state.route, state.arg, setRoute];
}

function ApprovalApp() {
  const [me, setMe] = React.useState(undefined);  // undefined = loading, null = unauth
  const [submissions, setSubmissions] = React.useState([]);
  const [route, routeArg, setRoute] = useRoute('home');

  // Global navigation helpers. Any screen can call these to jump
  // somewhere specific without having to thread navigation callbacks
  // through every list+row component.
  //   • openArtist(id)         → artist detail page
  //   • openHomeWithTag(tag)   → home library, pre-filtered by tag
  //                              (filter state is exposed via a
  //                              one-shot global the home screen
  //                              consumes on mount)
  React.useEffect(() => {
    window.openArtist = (identifier) => {
      if (!identifier) return;
      setRoute('artists', identifier);
    };
    window.openHomeWithTag = (tag) => {
      if (!tag) return;
      // Stash the desired tag in a one-shot global; HomeScreen reads
      // and clears it on mount. The route change has to come AFTER
      // so the home screen mounts WITH the global already set.
      window.__pendingHomeTag = tag;
      setRoute('home');
    };
    return () => {
      delete window.openArtist;
      delete window.openHomeWithTag;
    };
  }, [setRoute]);

  // Re-fetches everything the app reads from the backend: identity,
  // roster, full submissions list. Called on initial mount and after
  // any action that could mutate one of those — vote, accept,
  // profile save, block, etc.
  const refresh = React.useCallback(async () => {
    const [meRes, players, subs] = await Promise.all([
      Api.me().catch(() => null),
      Api.players(),
      Api.submissions(),
    ]);
    if (meRes) {
      Store.set({ me: meRes.player });
      setMe(meRes.player);
    }
    Store.set({
      players: players.players,
      verifiedSet: new Set(players.verifiedIdentifiers || []),
      submissions: subs,
    });
    setSubmissions(subs);
  }, []);

  React.useEffect(() => {
    (async () => {
      // Distinguish "no session" (real null) from "transient error"
      // (e.g. 429 during a bulk upload run). The latter throws an
      // error with `rateLimited: true` — we want to keep the user
      // logged in and just show a toast, NOT bounce them to login.
      let meRes;
      try {
        meRes = await Api.me();
      } catch (e) {
        if (e.rateLimited) {
          toast.error('Server ist gerade überlastet. Versuch es gleich nochmal.');
          return;   // keep `me` as undefined → LoadingScreen stays
        }
        meRes = null;
      }
      if (!meRes) { setMe(null); return; }
      Store.set({ me: meRes.player });
      setMe(meRes.player);
      await refresh();
    })();
  }, [refresh]);

  // Share-link boot path. When the URL is `#/track/<id>` we fetch
  // the track, hand it to AudioService.play(), and pop the big
  // player open via the global setter NowPlayingBar exposes. The
  // hash is then normalised back to `#/home` so a refresh doesn't
  // re-fire the auto-play. Also fires on later hashchange events
  // so a paste-into-address-bar mid-session works the same as a
  // fresh page load.
  React.useEffect(() => {
    if (!me) return;   // wait until session is resolved
    let cancelled = false;

    const tryPlayShared = async () => {
      const raw = (window.location.hash || '').replace(/^#\/?/, '');
      const m = raw.match(/^track\/(.+)$/);
      if (!m) return;
      const id = decodeURIComponent(m[1]);
      const track = await Api.track(id);
      if (cancelled) return;
      if (!track) {
        toast.error('Track nicht gefunden oder noch nicht freigegeben.');
        setRoute('home');
        return;
      }
      AudioService.play(track);
      // The NowPlayingBar exposes `expandBigPlayer` once mounted;
      // a few microtasks of leeway in case the bar hasn't run its
      // mount effect yet (first paint after login).
      const expand = () => window.expandBigPlayer?.();
      expand();
      setTimeout(expand, 50);
      // Clear the share hash so refreshes don't keep auto-playing.
      // Use replaceState to avoid a noisy history entry.
      history.replaceState(null, '', '#/home');
    };

    tryPlayShared();
    const onHash = () => tryPlayShared();
    window.addEventListener('hashchange', onHash);
    return () => { cancelled = true; window.removeEventListener('hashchange', onHash); };
  }, [me, setRoute]);

  // Send the user away from screens their identity can't access.
  // The fallback target depends on what they CAN do — voters
  // bounce to inbox, submitters to submit, anyone else lands on
  // /home which is always available now (browse-only).
  React.useEffect(() => {
    if (!me) return;
    const fallback = canVote(me) ? 'inbox' : 'home';
    if (route === 'inbox' && !canVote(me)) setRoute('home');
    // Admins always have access to /submit because the page hosts
    // the bulk-upload tool as an admin-only sub-mode — even if they
    // personally don't pass canSubmit (e.g. an admin who's actually
    // banned at the FiveM level), they still need the bulk surface.
    if (route === 'submit'  && !canSubmit(me) && !canForceAccept(me)) setRoute(fallback);
    if ((route === 'artists' || route === 'stats') && !canForceAccept(me)) setRoute(fallback);
    if (route === 'reports' && me.group !== 'admin' && me.group !== 'mod') setRoute(fallback);
    if (route === 'audit'   && me.group !== 'admin') setRoute(fallback);
  }, [me, route]);

  if (me === undefined) {
    return (
      <PageBg>
        <LoadingScreen/>
        <ToastContainer/>
      </PageBg>
    );
  }

  if (me === null) {
    return (
      <PageBg>
        <LoginScreen/>
        <ToastContainer/>
      </PageBg>
    );
  }

  return (
    <PageBg>
      <Header
        me={me} route={route} setRoute={setRoute}
        onLogout={async () => {
          await Api.logout();
          setMe(null);
          AudioService.stop();
          // Clear the per-user caches so the next user who logs in
          // on the same browser doesn't briefly see leftover state
          // (hearts on someone else's liked tracks, the previous
          // user's playlists in the +menu) before their own data
          // loads.
          Likes.reset();
          MyPlaylists.reset();
          toast.info('Abgemeldet.');
        }}
      />

      {/* page-anim key forces a fresh fade-in on every route change */}
      <div className="page-anim" key={route}>
        {route === 'home'    && <HomeScreen submissions={submissions} me={me} refresh={refresh}/>}
        {route === 'inbox'   && <ReviewQueue submissions={submissions} me={me} refresh={refresh}/>}
        {route === 'submit'  && <SubmitScreen me={me} setRoute={setRoute} refresh={refresh}/>}
        {route === 'playlists' && <PlaylistsScreen me={me} identifier={routeArg} setRoute={setRoute}/>}
        {route === 'reports' && <ReportsScreen me={me}/>}
        {route === 'audit'   && <AuditScreen me={me}/>}
        {route === 'stats'   && <StatsScreen me={me} refresh={refresh}/>}
        {route === 'artists' && <ArtistsScreen me={me} identifier={routeArg} setRoute={setRoute} refresh={refresh}/>}
        {route === 'me'      && <ProfileScreen me={me} submissions={submissions} refresh={refresh}/>}
      </div>

      {/* Persistent player. Mounted once at the app shell so it
          survives every route change — switching screens never
          interrupts playback. Hides itself when nothing's loaded. */}
      <NowPlayingBar/>

      <ToastContainer/>
    </PageBg>
  );
}

// Spinner-and-text panel for the brief moment between page load and
// the first successful /api/me response. Branded with the Pacifico
// wordmark so the "first impression" already feels like the product
// instead of a generic loader.
function LoadingScreen() {
  return (
    <div style={{
      minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center',
      flexDirection: 'column', gap: 18,
    }}>
      <div style={{
        width: 56, height: 56, borderRadius: 16,
        background: 'var(--grad-sunset)',
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        boxShadow: 'var(--glow-coral)',
      }}>
        <ApIcon name="music" size={24} color="var(--foam)"/>
      </div>
      <div style={{
        fontFamily: 'var(--font-display)', fontSize: 28, lineHeight: 1,
        background: 'var(--grad-sunset)',
        WebkitBackgroundClip: 'text', backgroundClip: 'text', color: 'transparent',
      }}>Storytime</div>
      <div style={{
        width: 36, height: 36, borderRadius: '50%',
        border: '3px solid rgba(196,154,255,0.18)',
        borderTopColor: 'var(--lila-400)',
        animation: 'spin 0.7s linear infinite',
      }}/>
      <div style={{ fontSize: 12, color: 'var(--fg-3)', letterSpacing: 0.5 }}>
        Musik lädt…
      </div>
    </div>
  );
}

window.ApprovalApp = ApprovalApp;
