// Künstler — admin directory page. Replaces the standalone "Sperren"
// route; ban management is folded into the per-artist detail view.
//
// Two states inside this single screen, controlled by the `identifier`
// prop coming from the URL hash:
//
//   • identifier == null  → directory list (paginated, sortable,
//                            filterable). Click an artist to open
//                            their detail page.
//   • identifier set      → detail view. Profile header + ban /
//                            unban action + their full track list.
//
// Both views fetch their own data; switching between them does NOT
// preserve the list's scroll position, but does preserve filter state
// (component stays mounted, just renders a different sub-tree).

const PAGE_SIZE = 50;

const SORT_OPTIONS = [
  { id: 'streams', label: 'Meist gehört' },
  { id: 'tracks',  label: 'Meiste Tracks' },
  { id: 'recent',  label: 'Letzter Upload' },
  { id: 'name',    label: 'Name A–Z' },
];

const FILTER_OPTIONS = [
  { id: 'all',       label: 'Alle' },
  { id: 'unblocked', label: 'Nicht gesperrt' },
  { id: 'blocked',   label: 'Gesperrt' },
];

function ArtistsScreen({ me, identifier, setRoute, refresh: refreshApp }) {
  // Gate at the route level too — defense-in-depth in case someone
  // pastes the URL without going through the header.
  if (me.group !== 'admin') {
    return (
      <div style={{ padding: '60px 32px 140px', maxWidth: 620, margin: '0 auto' }}>
        <Glass radius={20} padding={36} style={{ textAlign: 'center' }}>
          <ApIcon name="shield" size={32} color="var(--gold)"/>
          <h2 style={{ fontSize: 20, fontWeight: 800, margin: '12px 0 6px' }}>Nur Admins</h2>
        </Glass>
      </div>
    );
  }

  if (identifier) {
    return <ArtistDetail
      me={me} identifier={identifier}
      onBack={() => setRoute('artists')} refreshApp={refreshApp}/>;
  }
  return <ArtistsList me={me} onOpen={(id) => setRoute('artists', id)}/>;
}

// ─── LIST VIEW ──────────────────────────────────────────────────────

function ArtistsList({ me, onOpen }) {
  const [query, setQuery] = React.useState('');
  const [sort, setSort] = React.useState('streams');
  const [filter, setFilter] = React.useState('all');
  const [page, setPage] = React.useState(0);
  const [data, setData] = React.useState({ items: [], total: 0 });
  const [loading, setLoading] = React.useState(true);
  // Inline "manuell sperren" form — collapsed by default. Tap the
  // header button to expand and block someone by raw identifier even
  // when they have no track / profile yet (e.g. preemptive ban after
  // a Discord report).
  const [manualOpen, setManualOpen] = React.useState(false);
  const deferredQuery = React.useDeferredValue(query);

  const load = React.useCallback(async () => {
    setLoading(true);
    try {
      const out = await Api.artist.list({
        q: deferredQuery, sort, filter,
        limit: PAGE_SIZE, offset: page * PAGE_SIZE,
      });
      setData(out);
    } catch (e) {
      toast.error('Künstler-Liste laden fehlgeschlagen: ' + e.message);
    } finally {
      setLoading(false);
    }
  }, [deferredQuery, sort, filter, page]);

  React.useEffect(() => { load(); }, [load]);
  React.useEffect(() => { setPage(0); }, [deferredQuery, sort, filter]);

  const totalPages = Math.max(1, Math.ceil(data.total / PAGE_SIZE));

  return (
    <div style={{ padding: '32px 36px 140px', maxWidth: 1240, margin: '0 auto' }}>

      <div style={{
        display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between',
        gap: 22, marginBottom: 22, flexWrap: 'wrap',
      }}>
        <div style={{ minWidth: 0, flex: '1 1 480px' }}>
          <Eyebrow style={{ marginBottom: 6 }}>{numDE(data.total)} Künstler</Eyebrow>
          <h1 style={{ fontWeight: 800, fontSize: 40, lineHeight: 1.05, letterSpacing: '-0.02em', margin: 0 }}>
            Künstler
          </h1>
          <p style={{ fontSize: 14.5, color: 'var(--fg-2)', marginTop: 8, maxWidth: 640, opacity: 0.82 }}>
            Jeder Spieler der hochgeladen, einen Künstlernamen gesetzt oder eine
            Sperre kassiert hat. Klick einen Eintrag um Profil, Tracks und
            Sperr-Status zu sehen.
          </p>
        </div>
        <div style={{ width: 320, flexShrink: 0 }}>
          <TextInput value={query} onChange={e => setQuery(e.target.value)}
            placeholder="Nach Name oder ID suchen…" icon="search"/>
        </div>
      </div>

      {/* Filter chips + manual-block toggle + sort dropdown */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 14, flexWrap: 'wrap' }}>
        {FILTER_OPTIONS.map(o => (
          <FilterChip key={o.id} active={filter === o.id}
            color={o.id === 'blocked' ? 'var(--danger)' : 'var(--lila-300)'}
            onClick={() => setFilter(o.id)}>
            {o.label}
          </FilterChip>
        ))}
        <div style={{ flex: 1 }}/>
        <Button variant={manualOpen ? 'secondary' : 'ghost'} size="sm"
          icon={manualOpen ? 'x' : 'plus'}
          onClick={() => setManualOpen(o => !o)}>
          {manualOpen ? 'Schließen' : 'Manuell sperren'}
        </Button>
        <select value={sort} onChange={(e) => setSort(e.target.value)} style={{
          appearance: 'none', WebkitAppearance: 'none',
          padding: '8px 30px 8px 14px', borderRadius: 999,
          background: 'var(--bg-input)', border: '1px solid var(--border-soft)',
          color: 'var(--foam)', fontSize: 12.5, fontFamily: 'inherit', fontWeight: 600,
          outline: 'none', cursor: 'pointer',
        }}>
          {SORT_OPTIONS.map(o => (
            <option key={o.id} value={o.id} style={{ background: '#1F0E3D', color: 'var(--foam)' }}>
              {o.label}
            </option>
          ))}
        </select>
      </div>

      {/* Manuell-sperren expanded form. Renders only when toggled
          open so its inputs don't compete for focus while the user
          is searching above. */}
      {manualOpen && (
        <ManualBlockForm onBlocked={() => { setManualOpen(false); load(); }}/>
      )}

      <Glass radius={20} padding={0}>
        <div style={{
          display: 'grid',
          gridTemplateColumns: '52px 1fr 200px 110px 110px 100px',
          gap: 12, padding: '12px 22px',
          borderBottom: '1px solid var(--border-soft)',
          fontSize: 11, fontWeight: 700, letterSpacing: '0.18em',
          textTransform: 'uppercase', color: 'var(--fg-3)',
        }}>
          <div/>
          <div>Künstler</div>
          <div>Identifier</div>
          <div style={{ textAlign: 'right' }}>Tracks</div>
          <div style={{ textAlign: 'right' }}>Streams</div>
          <div style={{ textAlign: 'right' }}>Status</div>
        </div>

        {loading && data.items.length === 0 ? (
          <div style={{ padding: 28, textAlign: 'center', color: 'var(--fg-3)', fontSize: 13 }}>
            Lädt…
          </div>
        ) : data.items.length === 0 ? (
          <EmptyState
            bare padding={28}
            description={deferredQuery ? `Keine Treffer für „${deferredQuery}"` : 'Keine Künstler gefunden.'}
          />
        ) : data.items.map((a, i) => (
          <ArtistRow key={a.identifier} artist={a} onClick={() => onOpen(a.identifier)}
            border={i < data.items.length - 1}/>
        ))}

        {totalPages > 1 && (
          <div style={{
            display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 14,
            padding: '14px 22px', borderTop: '1px solid var(--border-hairline)', flexWrap: 'wrap',
          }}>
            <span style={{ fontSize: 12, color: 'var(--fg-3)' }}>
              <span style={{ fontVariantNumeric: 'tabular-nums', color: 'var(--fg-2)' }}>
                {page * PAGE_SIZE + 1}-{Math.min(data.total, (page + 1) * PAGE_SIZE)}
              </span>
              {' '}von{' '}
              <span style={{ fontVariantNumeric: 'tabular-nums', color: 'var(--fg-2)' }}>
                {numDE(data.total)}
              </span>
            </span>
            <div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
              <Button variant="ghost" size="sm" icon="chevron-left"
                disabled={page <= 0}
                onClick={() => setPage(p => Math.max(0, p - 1))}>
                Zurück
              </Button>
              <span style={{ fontSize: 12, color: 'var(--fg-3)', fontVariantNumeric: 'tabular-nums', padding: '0 8px' }}>
                Seite {page + 1} / {totalPages}
              </span>
              <Button variant="ghost" size="sm" iconRight="chevron-right"
                disabled={page >= totalPages - 1}
                onClick={() => setPage(p => Math.min(totalPages - 1, p + 1))}>
                Weiter
              </Button>
            </div>
          </div>
        )}
      </Glass>
    </div>
  );
}

// Single artist row in the list view. Whole row is a click surface
// that opens the detail page. Status pill shows whether they're
// blocked or active.
function ArtistRow({ artist, onClick, border }) {
  // Try to resolve the identifier to a current-roster player so we
  // can surface their role + verified status. Falls through to a
  // synthetic stub when the player isn't on the roster (e.g.
  // they're offline / left the server) — Avatar + name still render
  // from the artist data we already have.
  const roster = playerByIdent(artist.identifier);
  const player = roster || {
    identifier: artist.identifier,
    name:       artist.name || artist.identifier.slice(0, 6) + '…',
    avatar:     fallbackAvatar(artist.identifier),
  };
  return (
    <button onClick={onClick} style={{
      display: 'grid',
      gridTemplateColumns: '52px 1fr 200px 110px 110px 100px',
      gap: 12, padding: '12px 22px', width: '100%',
      background: 'transparent', border: 'none', cursor: 'pointer',
      color: 'inherit', textAlign: 'left', fontFamily: 'inherit',
      alignItems: 'center',
      borderBottom: border ? '1px solid var(--border-hairline)' : 'none',
      transition: 'background 150ms var(--ease-out)',
    }}
    onMouseEnter={(e) => { e.currentTarget.style.background = 'rgba(196,154,255,0.06)'; }}
    onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent'; }}
    >
      {artist.avatarUrl ? (
        <div style={{
          width: 36, height: 36, borderRadius: '50%', flexShrink: 0,
          background: `url(${artist.avatarUrl}) center/cover`,
        }}/>
      ) : (
        <Avatar player={player} size={36}/>
      )}
      <div style={{ minWidth: 0 }}>
        <div style={{
          fontSize: 14, fontWeight: 700, color: 'var(--foam)',
          display: 'flex', alignItems: 'center', gap: 6,
        }}>
          <span style={{
            whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', minWidth: 0,
          }}>
            {artist.name || <span style={{ color: 'var(--fg-3)', fontStyle: 'italic' }}>Kein Profilname</span>}
          </span>
          {/* Verified + admin badges. Only render when we resolved
              the identifier to a roster player — without that we
              don't know roles. */}
          {roster && isVerifiedArtist(roster.identifier) && (
            <ApIcon name="verified" size={13} color="var(--aqua)"/>
          )}
          {roster?.group === 'admin' && (
            <ApIcon name="shield" size={12} color="var(--gold)"/>
          )}
        </div>
        {artist.lastSubmittedAt && (
          <div style={{ fontSize: 11.5, color: 'var(--fg-3)' }}>
            Letzter Upload {timeAgo(artist.lastSubmittedAt)}
          </div>
        )}
      </div>
      <div style={{
        fontFamily: 'var(--font-mono)', fontSize: 11.5, color: 'var(--fg-3)',
        whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
      }}>{artist.identifier}</div>
      <div style={{ fontSize: 13, color: 'var(--fg-2)', textAlign: 'right', fontVariantNumeric: 'tabular-nums' }}>
        {numDE(artist.trackCount)}
      </div>
      <div style={{
        fontSize: 13, color: 'var(--fg-2)', textAlign: 'right',
        fontVariantNumeric: 'tabular-nums',
      }}>{numDE(artist.totalStreams)}</div>
      <div style={{ textAlign: 'right' }}>
        {artist.blocked ? (
          <span style={{
            display: 'inline-flex', alignItems: 'center', gap: 4, padding: '3px 10px',
            borderRadius: 999, background: 'rgba(255,110,138,0.18)', color: 'var(--danger)',
            fontSize: 10.5, fontWeight: 700, letterSpacing: 0.4, textTransform: 'uppercase',
            border: '1px solid rgba(255,110,138,0.45)',
          }}>
            <ApIcon name="ban" size={9}/> Gesperrt
          </span>
        ) : (
          <span style={{
            display: 'inline-flex', alignItems: 'center', gap: 4, padding: '3px 10px',
            borderRadius: 999, background: 'rgba(95,216,166,0.16)', color: 'var(--success)',
            fontSize: 10.5, fontWeight: 700, letterSpacing: 0.4, textTransform: 'uppercase',
            border: '1px solid rgba(95,216,166,0.4)',
          }}>
            <ApIcon name="check" size={9}/> Aktiv
          </span>
        )}
      </div>
    </button>
  );
}

// ─── DETAIL VIEW ────────────────────────────────────────────────────

function ArtistDetail({ me, identifier, onBack, refreshApp }) {
  const audio = useAudioStatus();
  const [data, setData] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [busy, setBusy] = React.useState(false);

  const load = React.useCallback(async () => {
    setLoading(true);
    try {
      const out = await Api.artist.detail(identifier);
      setData(out);
    } catch (e) {
      toast.error('Künstler laden fehlgeschlagen: ' + e.message);
      setData(null);
    } finally {
      setLoading(false);
    }
  }, [identifier]);

  React.useEffect(() => { load(); }, [load]);

  const refreshAll = async () => {
    if (refreshApp) await refreshApp();
    await load();
  };

  // Block — pops a prompt for the reason. The blocks API already
  // requires admin + a clean identifier, so this just forwards.
  const onBlock = async () => {
    if (!data) return;
    const reason = prompt(
      `Spieler sperren?\n\nGesperrte Spieler können keine Tracks mehr hochladen, ` +
      `bis ein Admin sie wieder freigibt. Auf dem Server selbst werden sie ` +
      `nicht eingeschränkt.\n\nOptionaler Grund:`
    );
    if (reason === null) return;
    setBusy(true);
    try {
      await Api.blocks.add(
        identifier,
        reason.trim() || null,
        data.profile?.name || null
      );
      toast.info('Spieler gesperrt.');
      await refreshAll();
    } catch (e) {
      toast.error('Sperren fehlgeschlagen: ' + e.message);
    } finally {
      setBusy(false);
    }
  };

  const onUnblock = async () => {
    if (!data?.blocked) return;
    if (!confirm(`Sperre für ${data.profile?.name || identifier} aufheben?`)) return;
    setBusy(true);
    try {
      await Api.blocks.remove(identifier);
      toast.success('Sperre aufgehoben.');
      await refreshAll();
    } catch (e) {
      toast.error('Aufheben fehlgeschlagen: ' + e.message);
    } finally {
      setBusy(false);
    }
  };

  if (loading && !data) {
    return (
      <div style={{ padding: '60px 32px', textAlign: 'center', color: 'var(--fg-3)' }}>
        Lade Künstler…
      </div>
    );
  }
  if (!data) {
    return (
      <div style={{ padding: '60px 32px 140px', maxWidth: 620, margin: '0 auto' }}>
        <Glass radius={20} padding={36} style={{ textAlign: 'center' }}>
          <ApIcon name="circle-question" size={32} color="var(--fg-3)"/>
          <h2 style={{ fontSize: 20, fontWeight: 800, margin: '12px 0 6px' }}>
            Künstler nicht gefunden
          </h2>
          <Button variant="secondary" size="sm" icon="chevron-left" onClick={onBack}>
            Zurück
          </Button>
        </Glass>
      </div>
    );
  }

  // Same roster-lookup pattern as the list rows — gives us role +
  // verified info if we have the player loaded.
  const roster = playerByIdent(data.identifier);
  const player = roster || {
    identifier: data.identifier,
    name:       data.profile?.name || identifier.slice(0, 6) + '…',
    avatar:     fallbackAvatar(data.identifier),
  };

  return (
    <div style={{ padding: '32px 36px 140px', maxWidth: 1100, margin: '0 auto' }}>

      {/* Back navigation */}
      <button onClick={onBack} style={{
        display: 'inline-flex', alignItems: 'center', gap: 8,
        background: 'transparent', border: 'none', padding: '6px 0',
        cursor: 'pointer', color: 'var(--fg-2)', fontSize: 13.5, fontWeight: 600,
        fontFamily: 'inherit', marginBottom: 14,
      }}>
        <ApIcon name="chevron-left" size={12}/> Zurück zur Liste
      </button>

      {/* Identity card */}
      <Glass radius={24} padding={28} style={{ marginBottom: 18 }}
        orb={data.blocked ? 'var(--danger)' : 'var(--magenta)'}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 22, flexWrap: 'wrap' }}>
          {data.profile?.avatarUrl ? (
            <div style={{
              width: 88, height: 88, borderRadius: '50%', flexShrink: 0,
              background: `url(${data.profile.avatarUrl}) center/cover`,
              boxShadow: '0 6px 18px rgba(20,8,40,0.45)',
            }}/>
          ) : (
            <Avatar player={player} size={88}/>
          )}
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 6, flexWrap: 'wrap' }}>
              <h1 style={{ fontWeight: 800, fontSize: 32, lineHeight: 1.05, letterSpacing: '-0.02em', margin: 0 }}>
                {data.profile?.name || <span style={{ color: 'var(--fg-3)', fontStyle: 'italic' }}>Kein Profilname</span>}
              </h1>
              {/* Role / verified pill. Only renders when we
                  resolved the identifier to a roster player —
                  RolePill itself handles the verified-but-no-role
                  case via isVerifiedArtist. */}
              {roster && <RolePill player={roster}/>}
              {data.blocked && (
                <span style={{
                  display: 'inline-flex', alignItems: 'center', gap: 5, padding: '3px 10px',
                  borderRadius: 999, background: 'rgba(255,110,138,0.18)', color: 'var(--danger)',
                  fontSize: 10.5, fontWeight: 700, letterSpacing: 0.4, textTransform: 'uppercase',
                  border: '1px solid rgba(255,110,138,0.45)',
                }}>
                  <ApIcon name="ban" size={11}/> Gesperrt
                </span>
              )}
            </div>
            <div style={{ display: 'flex', alignItems: 'center', gap: 14, flexWrap: 'wrap', color: 'var(--fg-2)' }}>
              <IdentifierMono identifier={data.identifier} size={12}/>
              <span style={{ color: 'var(--fg-4)' }}>·</span>
              <span style={{ fontSize: 13 }}>
                <b style={{ color: 'var(--foam)' }}>{numDE(data.trackCount)}</b> Tracks
              </span>
              <span style={{ color: 'var(--fg-4)' }}>·</span>
              <span style={{ fontSize: 13 }}>
                <b style={{ color: 'var(--foam)' }}>{numDE(data.totalStreams)}</b> Streams
              </span>
            </div>
          </div>

          {/* Block / unblock action. Self-block is suppressed —
              even if the server would refuse, the visible disabled
              state is clearer than waiting for an error toast. */}
          {data.identifier === me.identifier ? (
            <div style={{
              padding: '8px 14px', borderRadius: 12,
              background: 'rgba(196,154,255,0.08)',
              border: '1px solid var(--border-soft)',
              fontSize: 12.5, color: 'var(--fg-3)',
              display: 'inline-flex', alignItems: 'center', gap: 6,
            }}>
              <ApIcon name="user" size={11}/> Das bist du
            </div>
          ) : data.blocked ? (
            <Button variant="secondary" size="md" icon="unlock"
              disabled={busy} onClick={onUnblock}>
              Sperre aufheben
            </Button>
          ) : (
            <Button variant="danger" size="md" icon="ban"
              disabled={busy} onClick={onBlock}>
              Sperren
            </Button>
          )}
        </div>

        {/* Block details if blocked */}
        {data.blocked && (
          <div style={{
            marginTop: 22, padding: '14px 16px', borderRadius: 14,
            background: 'rgba(255,110,138,0.10)',
            border: '1px solid rgba(255,110,138,0.3)',
          }}>
            <Eyebrow style={{ marginBottom: 6, color: 'var(--danger)' }}>Sperrgrund</Eyebrow>
            <div style={{ fontSize: 13.5, color: 'var(--foam)', lineHeight: 1.5 }}>
              {data.blocked.reason || <span style={{ color: 'var(--fg-3)', fontStyle: 'italic' }}>Kein Grund angegeben</span>}
            </div>
            <div style={{ fontSize: 11.5, color: 'var(--fg-3)', marginTop: 8 }}>
              Gesperrt {timeAgo(data.blocked.at)}
              {data.blocked.by && (
                <> · von <PlayerName player={playerByIdent(data.blocked.by)} size={11.5} opacity={1}/></>
              )}
            </div>
          </div>
        )}
      </Glass>

      {/* Tracks list */}
      <Glass radius={20} padding={0}>
        <div style={{
          padding: '14px 20px',
          borderBottom: '1px solid var(--border-soft)',
          display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        }}>
          <Eyebrow>Tracks · {data.tracks.length}</Eyebrow>
        </div>
        {data.tracks.length === 0 ? (
          <div style={{ padding: 28, textAlign: 'center', color: 'var(--fg-3)', fontSize: 13 }}>
            Dieser Künstler hat noch keine Tracks hochgeladen.
          </div>
        ) : data.tracks.map((s, i) => {
          const t = tally(s);
          // Build the meta line — timeAgo + streams + first 3 tags
          // (joined with the same separator dots the row uses).
          const metaBits = [timeAgo(s.submittedAt)];
          if (s.streams > 0) metaBits.push(`${numDE(s.streams)} Streams`);
          if (s.tags?.length > 0) metaBits.push(...s.tags.slice(0, 3));
          return (
            <TrackRow key={s.id} track={s}
              border={i < data.tracks.length - 1}
              meta={metaBits.join(' · ')}
              trailing={
                <>
                  <div style={{
                    fontSize: 11.5, color: 'var(--fg-3)', minWidth: 80, textAlign: 'right',
                    fontVariantNumeric: 'tabular-nums',
                  }}>
                    <span style={{ color: 'var(--success)' }}>{t.accepts}</span> /{' '}
                    <span style={{ color: 'var(--danger)' }}>{t.rejects}</span>
                  </div>
                  <StatusPill status={s.status}/>
                  <ShareButton track={s}/>
                  <AdminMenu sub={s} refresh={refreshAll}/>
                </>
              }/>
          );
        })}
      </Glass>
    </div>
  );
}

// Inline manual-block form. Used when an admin wants to sperren an
// identifier that has NO track / profile / existing block — e.g. a
// preemptive ban based on a Discord report before the player has
// even tried to upload. After a successful POST the parent re-loads
// its list so the new entry appears.
//
// Identifier validation mirrors the server: 16-64 hex chars, no
// "license:" prefix. The server re-validates on its own — this is
// just so we can surface an error before the round-trip.
function ManualBlockForm({ onBlocked }) {
  const [identifier, setIdentifier] = React.useState('');
  const [name, setName] = React.useState('');
  const [reason, setReason] = React.useState('');
  const [busy, setBusy] = React.useState(false);

  const submit = async (e) => {
    e.preventDefault();
    const id = identifier.trim().toLowerCase();
    if (!/^[a-f0-9]{16,64}$/.test(id)) {
      toast.error('Identifier muss 16–64 Hex-Zeichen sein, ohne „license:"-Prefix.');
      return;
    }
    setBusy(true);
    try {
      await Api.blocks.add(id, reason.trim() || null, name.trim() || null);
      toast.success('Spieler gesperrt.');
      setIdentifier(''); setName(''); setReason('');
      if (onBlocked) onBlocked();
    } catch (ex) {
      toast.error('Sperren fehlgeschlagen: ' + ex.message);
    } finally {
      setBusy(false);
    }
  };

  return (
    <Glass radius={16} padding={18} style={{
      marginBottom: 18,
      border: '1px solid rgba(255,110,138,0.4)',
      background: 'rgba(255,110,138,0.06)',
    }}>
      <Eyebrow style={{ marginBottom: 10, color: 'var(--danger)' }}>
        Spieler ohne Künstler-Account sperren
      </Eyebrow>
      <form onSubmit={submit}
        style={{ display: 'grid', gridTemplateColumns: '2fr 2fr 3fr auto', gap: 10, alignItems: 'start' }}>
        <TextInput
          value={identifier} onChange={e => setIdentifier(e.target.value)}
          placeholder="FiveM-Identifier (Hex, ohne license:)"
          icon="fingerprint"
          spellCheck={false}
          style={{ fontFamily: 'var(--font-mono)', fontSize: 13 }}
        />
        <TextInput
          value={name} onChange={e => setName(e.target.value)}
          placeholder="Spielername (optional)" maxLength={80}
        />
        <TextInput
          value={reason} onChange={e => setReason(e.target.value)}
          placeholder="Grund (optional)" maxLength={500}
        />
        <Button type="submit" variant="danger" size="md" icon="ban"
          disabled={busy || !identifier.trim()}>
          {busy ? '…' : 'Sperren'}
        </Button>
      </form>
      <div style={{ fontSize: 11.5, color: 'var(--fg-3)', marginTop: 8 }}>
        Der Spieler kann keine Tracks mehr hochladen, bis die Sperre über dessen Detail-Seite wieder aufgehoben wird.
      </div>
    </Glass>
  );
}

window.ArtistsScreen = ArtistsScreen;
