import {
  Archive,
  ArrowRight,
  Bell,
  BookOpen,
  Camera,
  ChevronDown,
  ChevronRight,
  Clock3,
  Coins,
  Copy,
  Heart,
  House,
  LogIn,
  MessageCircle,
  Pencil,
  Search,
  Settings,
  SlidersHorizontal,
  Sparkles,
  Star,
  Ticket,
  Trophy,
  UserPlus,
  UserRound,
} from 'lucide-react';
import {
  type ComponentType,
  type PointerEvent,
  type ReactNode,
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
} from 'react';

import communityQqQrImage from '../../../media/social-media-group/qq.png';
import communityWechatQrImage from '../../../media/social-media-group/wechat.png';
import type {
  CustomWorldLibraryEntry,
  PlatformBrowseHistoryEntry,
  ProfileDashboardCardKey,
  ProfileDashboardSummary,
  ProfilePlayedWorkSummary,
  ProfilePlayStatsResponse,
  ProfileReferralInviteCenterResponse,
  ProfileSaveArchiveSummary,
  ProfileWalletLedgerResponse,
  RedeemProfileReferralInviteCodeResponse,
  RedeemProfileRewardCodeResponse,
} from '../../../packages/shared/src/contracts/runtime';
import type { HydratedSavedGameSnapshot } from '../../persistence/runtimeSnapshotTypes';
import type { AuthUser } from '../../services/authService';
import { updateAuthProfile } from '../../services/authService';
import { copyTextToClipboard } from '../../services/clipboard';
import {
  getRpgProfileReferralInviteCenter,
  getRpgProfileWalletLedger,
  redeemRpgProfileReferralInviteCode,
  redeemRpgProfileRewardCode,
} from '../../services/rpg-entry/rpgProfileClient';
import type { CustomWorldProfile } from '../../types';
import { useAuthUi } from '../auth/AuthUiContext';
import { ResolvedAssetImage } from '../ResolvedAssetImage';
import { RpgEntryBrandLogo } from './RpgEntryBrandLogo';
import {
  buildPlatformWorldDisplayTags,
  describePlatformThemeLabel,
  formatPlatformWorkDisplayName,
  formatPlatformWorkDisplayTag,
  formatPlatformWorldTime,
  isBigFishGalleryEntry,
  isPuzzleGalleryEntry,
  type PlatformPublicGalleryCard,
  type PlatformWorldCardLike,
  resolvePlatformWorldCoverImage,
  resolvePlatformWorldLeadPortrait,
} from './rpgEntryWorldPresentation';

export type PlatformHomeTab =
  | 'home'
  | 'category'
  | 'create'
  | 'saves'
  | 'profile';
export interface RpgEntryHomeViewProps {
  activeTab: PlatformHomeTab;
  onTabChange: (tab: PlatformHomeTab) => void;
  hasSavedGame: boolean;
  savedSnapshot: HydratedSavedGameSnapshot | null;
  saveEntries: ProfileSaveArchiveSummary[];
  saveError: string | null;
  featuredEntries: PlatformPublicGalleryCard[];
  latestEntries: PlatformPublicGalleryCard[];
  myEntries: CustomWorldLibraryEntry<CustomWorldProfile>[];
  historyEntries: PlatformBrowseHistoryEntry[];
  profileDashboard: ProfileDashboardSummary | null;
  isLoadingPlatform: boolean;
  isLoadingDashboard: boolean;
  isResumingSaveWorldKey: string | null;
  platformError: string | null;
  dashboardError: string | null;
  onContinueGame: (snapshot?: HydratedSavedGameSnapshot | null) => void;
  onResumeSave: (entry: ProfileSaveArchiveSummary) => void;
  onOpenCreateWorld: () => void;
  onOpenCreateTypePicker: () => void;
  onOpenGalleryDetail: (entry: PlatformPublicGalleryCard) => void;
  onOpenLibraryDetail: (
    entry: CustomWorldLibraryEntry<CustomWorldProfile>,
  ) => void;
  onDeleteLibraryEntry?: (
    entry: CustomWorldLibraryEntry<CustomWorldProfile>,
  ) => void;
  deletingLibraryEntryId?: string | null;
  onSearchPublicCode?: (keyword: string) => void | Promise<void>;
  isSearchingPublicCode?: boolean;
  onOpenProfileDashboardCard?: (cardKey: ProfileDashboardCardKey) => void;
  profilePlayStats?: ProfilePlayStatsResponse | null;
  isProfilePlayStatsOpen?: boolean;
  isProfilePlayStatsLoading?: boolean;
  profilePlayStatsError?: string | null;
  onCloseProfilePlayStats?: () => void;
  onOpenPlayedWork?: (work: ProfilePlayedWorkSummary) => void;
  onRechargeSuccess?: () => void | Promise<void>;
  createTabContent?: ReactNode;
}

const PANEL_SURFACE_CLASS = 'platform-surface platform-surface--soft';
const HERO_SURFACE_CLASS =
  'platform-surface platform-surface--hero platform-interactive-card min-w-0';
const MOBILE_PAGE_STAGE_CLASS =
  'platform-page-stage platform-remap-surface min-w-0 space-y-4 overflow-hidden pb-2';
const DESKTOP_PAGE_STAGE_CLASS =
  'platform-page-stage platform-remap-surface min-w-0 space-y-5 pb-4';
const DESKTOP_LAYOUT_QUERY = '(min-width: 1024px)';
const PLATFORM_HOME_TABS: PlatformHomeTab[] = [
  'home',
  'category',
  'create',
  'saves',
  'profile',
];
const AVATAR_MAX_FILE_SIZE = 5 * 1024 * 1024;
const AVATAR_OUTPUT_SIZE = 256;
const AVATAR_ALLOWED_TYPES = new Set(['image/jpeg', 'image/png', 'image/webp']);
type ProfilePopupPanel = 'invite' | 'redeem' | 'community';
type MobileHomeChannel = 'recommend' | 'today' | 'category';
type PlatformRankingTab = 'hot' | 'remix' | 'new' | 'like';

const COMMUNITY_QR_CODES = [
  {
    label: '微信群',
    src: communityWechatQrImage,
    alt: '玩家社区微信群二维码',
  },
  {
    label: 'QQ群',
    src: communityQqQrImage,
    alt: '玩家社区 QQ 群二维码',
  },
] as const;

const MOBILE_HOME_CHANNELS: Array<{
  id: MobileHomeChannel;
  label: string;
}> = [
  { id: 'recommend', label: '推荐' },
  { id: 'today', label: '今日游戏' },
  { id: 'category', label: '游戏分类' },
];

const PLATFORM_RANKING_TABS: Array<{
  id: PlatformRankingTab;
  label: string;
  metricLabel: string;
  emptyText: string;
}> = [
  {
    id: 'hot',
    label: '热门榜',
    metricLabel: '游玩',
    emptyText: '公开广场暂时还没有热门作品。',
  },
  {
    id: 'remix',
    label: '改造榜',
    metricLabel: '改造',
    emptyText: '公开广场暂时还没有改造作品。',
  },
  {
    id: 'new',
    label: '新品榜',
    metricLabel: '近7日',
    emptyText: '近 7 日暂时还没有新品。',
  },
  {
    id: 'like',
    label: '点赞榜',
    metricLabel: '点赞',
    emptyText: '公开广场暂时还没有点赞作品。',
  },
];
function usePlatformDesktopLayout() {
  const [isDesktopLayout, setIsDesktopLayout] = useState(() => {
    if (
      typeof window === 'undefined' ||
      typeof window.matchMedia !== 'function'
    ) {
      return false;
    }

    return window.matchMedia(DESKTOP_LAYOUT_QUERY).matches;
  });

  useEffect(() => {
    if (
      typeof window === 'undefined' ||
      typeof window.matchMedia !== 'function'
    ) {
      return;
    }

    const mediaQuery = window.matchMedia(DESKTOP_LAYOUT_QUERY);
    const updateLayout = (event?: MediaQueryListEvent) => {
      setIsDesktopLayout(event?.matches ?? mediaQuery.matches);
    };

    updateLayout();

    // 平台页只挂载当前断点外壳，避免隐藏的移动端/桌面端内容重复抢占查询。
    if (typeof mediaQuery.addEventListener === 'function') {
      mediaQuery.addEventListener('change', updateLayout);
      return () => mediaQuery.removeEventListener('change', updateLayout);
    }

    mediaQuery.addListener(updateLayout);
    return () => mediaQuery.removeListener(updateLayout);
  }, []);

  return isDesktopLayout;
}

function ResolvedAssetBackdrop({
  src,
  alt,
  className,
  ariaHidden = false,
}: {
  src?: string | null;
  alt: string;
  className: string;
  ariaHidden?: boolean;
}) {
  return (
    <ResolvedAssetImage
      src={src}
      alt={alt}
      aria-hidden={ariaHidden}
      className={className}
    />
  );
}

function SectionHeader({ title, detail }: { title: string; detail: string }) {
  return (
    <div className="mb-3">
      <div className="text-[10px] font-semibold tracking-[0.26em] text-[var(--platform-text-soft)]">
        {detail}
      </div>
      <div className="mt-1 text-base font-semibold text-[var(--platform-text-strong)]">
        {title}
      </div>
    </div>
  );
}

function PublicCodeSearchBar({
  value,
  onChange,
  onSubmit,
  isSearching,
  className,
}: {
  value: string;
  onChange: (value: string) => void;
  onSubmit: () => void;
  isSearching: boolean;
  className?: string;
}) {
  return (
    <div
      className={`platform-desktop-search flex min-w-0 items-center gap-3 px-4 py-3 text-[var(--platform-text-soft)] ${className ?? ''}`}
    >
      <Search className="h-4 w-4 shrink-0" />
      <input
        value={value}
        onChange={(event) => onChange(event.target.value)}
        onKeyDown={(event) => {
          if (event.key === 'Enter') {
            event.preventDefault();
            onSubmit();
          }
        }}
        placeholder="输入 SY / CW / BF / PZ 编号"
        className="w-full min-w-0 bg-transparent text-sm text-[var(--platform-text-strong)] outline-none placeholder:text-[var(--platform-text-soft)]"
      />
      <button
        type="button"
        onClick={onSubmit}
        disabled={!value.trim() || isSearching}
        className="shrink-0 text-xs font-semibold text-[var(--platform-text-soft)] disabled:opacity-50"
      >
        {isSearching ? '搜索中' : '搜索'}
      </button>
    </div>
  );
}

function EmptyShelf({ text }: { text: string }) {
  return (
    <div
      className={`${PANEL_SURFACE_CLASS} min-w-0 rounded-[1.35rem] px-4 py-3 text-sm leading-6 text-[var(--platform-text-base)]`}
    >
      {text}
    </div>
  );
}

function SaveArchivePreview({
  entry,
  label,
  className,
}: {
  entry: ProfileSaveArchiveSummary;
  label: string;
  className: string;
}) {
  return (
    <div
      aria-hidden="true"
      className={`platform-remap-surface relative shrink-0 overflow-hidden rounded-[1.35rem] border border-white/12 bg-black/18 shadow-[var(--platform-desktop-hover-shadow)] ${className}`}
    >
      {entry.coverImageSrc ? (
        <ResolvedAssetBackdrop
          src={entry.coverImageSrc}
          alt=""
          className="absolute inset-0 h-full w-full object-cover"
        />
      ) : (
        <div className="absolute inset-0 bg-[radial-gradient(circle_at_top_left,rgba(255,255,255,0.18),transparent_34%),linear-gradient(145deg,rgba(255,94,125,0.92),rgba(255,150,116,0.88))]" />
      )}
      <div className="absolute inset-0 bg-[var(--platform-card-overlay-soft)]" />
      <div className="absolute inset-x-0 bottom-0 px-2.5 py-2">
        <span className="inline-flex max-w-full items-center rounded-full border border-white/15 bg-black/24 px-2.5 py-1 text-[9px] font-semibold tracking-[0.14em] text-[var(--platform-text-strong)]">
          {label}
        </span>
      </div>
    </div>
  );
}

function WorldCard({
  entry,
  badge,
  metaLabel,
  onClick,
  className,
}: {
  entry: PlatformWorldCardLike;
  badge: string;
  metaLabel: string;
  onClick: () => void;
  className?: string;
}) {
  const coverImage = resolvePlatformWorldCoverImage(entry);
  const displayName = formatPlatformWorkDisplayName(entry.worldName);
  const tags = buildPlatformWorldDisplayTags(entry, 3);
  const likeCount = getPlatformWorldLikeCount(entry);
  const cardLabel = `${entry.worldName}，${formatCompactCount(likeCount)}点赞`;

  return (
    <button
      type="button"
      onClick={onClick}
      aria-label={cardLabel}
      className={`platform-public-work-card platform-surface platform-interactive-card relative flex w-[min(21rem,88vw)] shrink-0 flex-col overflow-hidden p-0 text-left ${className ?? ''}`}
    >
      <div className="platform-public-work-card__cover relative aspect-video overflow-hidden">
        {coverImage ? (
          <ResolvedAssetBackdrop
            src={coverImage}
            alt={entry.worldName}
            className="absolute inset-0 h-full w-full object-cover"
          />
        ) : (
          <div className="absolute inset-0 bg-[radial-gradient(circle_at_18%_16%,rgba(255,255,255,0.28),transparent_30%),linear-gradient(135deg,rgba(255,118,117,0.42),rgba(89,164,255,0.34))]" />
        )}
        <div className="absolute inset-0 bg-[linear-gradient(180deg,rgba(0,0,0,0.02),rgba(0,0,0,0.18))]" />
        <div className="absolute left-3 top-3 flex min-w-0 max-w-[calc(100%-1.5rem)] flex-wrap gap-1.5">
          <span className="platform-pill platform-pill--warm max-w-[9rem] truncate px-2.5">
            {badge}
          </span>
          <span className="platform-pill platform-pill--neutral max-w-[9rem] truncate px-2.5">
            {metaLabel}
          </span>
        </div>
      </div>

      <div className="platform-public-work-card__body flex min-h-[7.25rem] flex-col gap-2 px-3.5 py-3">
        <div className="flex min-w-0 items-start justify-between gap-3">
          <div className="min-w-0 flex-1">
            <div className="line-clamp-1 break-words text-base font-black leading-tight text-[var(--platform-text-strong)]">
              {displayName}
            </div>
            {entry.subtitle ? (
              <div className="mt-0.5 line-clamp-1 break-words text-[11px] font-medium text-[var(--platform-text-soft)]">
                {entry.subtitle}
              </div>
            ) : null}
          </div>
          <div className="platform-public-work-card__likes shrink-0 text-right">
            <div className="flex items-center justify-end gap-1 text-xs font-black text-[var(--platform-warm-text)]">
              <Heart className="h-3.5 w-3.5 fill-current" />
              <span>{formatCompactCount(likeCount)}</span>
            </div>
            <div className="mt-0.5 text-[10px] font-semibold text-[var(--platform-text-soft)]">
              点赞
            </div>
          </div>
        </div>

        <div className="line-clamp-2 break-words text-xs leading-5 text-[color:color-mix(in_srgb,var(--platform-text-base)_88%,transparent)]">
          {entry.summaryText || entry.subtitle || '等待补充世界摘要。'}
        </div>

        <div className="mt-auto flex min-w-0 flex-wrap gap-1.5">
          {tags.length > 0 ? (
            tags.map((tag, index) => (
              <span
                key={`world-tag-${index}-${tag || 'empty'}`}
                className="platform-pill platform-pill--neutral max-w-full px-2.5"
              >
                <span className="truncate">{tag}</span>
              </span>
            ))
          ) : (
            <span className="platform-pill platform-pill--neutral px-2.5">
              {describePublicGalleryCardKind(entry)}
            </span>
          )}
        </div>
      </div>
    </button>
  );
}

function CreationLibraryCard({
  entry,
  onClick,
  onDelete,
  isDeleting = false,
}: {
  entry: CustomWorldLibraryEntry<CustomWorldProfile>;
  onClick: () => void;
  onDelete?: () => void;
  isDeleting?: boolean;
}) {
  const coverImage = resolvePlatformWorldCoverImage(entry);
  const leadPortrait = resolvePlatformWorldLeadPortrait(entry);
  const statusLabel = entry.visibility === 'published' ? '已发布' : '草稿';
  const metaLabel =
    entry.visibility === 'published'
      ? formatPlatformWorldTime(entry.publishedAt)
      : '仅自己可见';
  const primaryTag =
    buildPlatformWorldDisplayTags(entry, 1)[0] ??
    formatPlatformWorkDisplayTag(describePlatformThemeLabel(entry.themeMode));
  const displayName = formatPlatformWorkDisplayName(entry.worldName);
  const summaryText =
    entry.summaryText || entry.subtitle || '继续补完这个世界的设定与游玩入口。';

  return (
    <button
      type="button"
      onClick={onClick}
      className="platform-surface platform-interactive-card relative flex min-h-[13rem] w-full min-w-0 flex-col overflow-hidden px-3 py-3 text-left sm:min-h-[14rem] sm:px-3.5 sm:py-3.5"
    >
      {coverImage ? (
        <ResolvedAssetBackdrop
          src={coverImage}
          alt={entry.worldName}
          className="absolute inset-0 h-full w-full object-cover opacity-38"
        />
      ) : null}
      {leadPortrait ? (
        <ResolvedAssetImage
          src={leadPortrait}
          alt=""
          aria-hidden="true"
          className="absolute bottom-1.5 right-1.5 h-16 w-16 object-contain opacity-24 sm:h-20 sm:w-20"
        />
      ) : null}
      <div className="absolute inset-0 bg-[var(--platform-card-overlay-strong)]" />
      {onDelete ? (
        <button
          type="button"
          onClick={(event) => {
            event.stopPropagation();
            onDelete();
          }}
          disabled={isDeleting}
          className="platform-button platform-button--danger absolute right-2 top-2 z-20 min-h-0 rounded-full px-2.5 py-1 text-[10px] disabled:cursor-not-allowed disabled:opacity-60"
        >
          {isDeleting ? '删除中' : '删除'}
        </button>
      ) : null}
      <div className="relative z-10 flex h-full min-w-0 flex-col">
        <div className="flex min-w-0 flex-wrap items-center gap-1.5">
          <span
            className={`inline-flex items-center rounded-full border px-2 py-1 text-[10px] font-semibold tracking-[0.12em] ${
              entry.visibility === 'published'
                ? 'border-emerald-300/25 bg-emerald-500/12 text-emerald-50'
                : 'border-amber-300/25 bg-amber-500/12 text-amber-50'
            }`}
          >
            {statusLabel}
          </span>
          <span className="inline-flex min-w-0 max-w-full items-center rounded-full border border-white/12 bg-black/18 px-2 py-1 text-[10px] font-medium text-[var(--platform-text-base)]">
            <span className="truncate">{metaLabel}</span>
          </span>
        </div>

        <div className="mt-auto min-w-0">
          <div className="line-clamp-2 break-words text-base font-black leading-[1.15] text-[var(--platform-text-strong)] sm:text-[1.12rem]">
            {displayName}
          </div>
          {entry.subtitle ? (
            <div className="mt-1 line-clamp-1 break-words text-[11px] tracking-[0.08em] text-[color:color-mix(in_srgb,var(--platform-text-base)_84%,transparent)]">
              {entry.subtitle}
            </div>
          ) : null}
          <div className="mt-2 line-clamp-3 break-words text-[11px] leading-5 text-[color:color-mix(in_srgb,var(--platform-text-base)_88%,transparent)] sm:text-xs">
            {summaryText}
          </div>
          <div className="mt-3 flex flex-wrap items-center gap-1.5">
            <span className="inline-flex min-w-0 max-w-full items-center rounded-full border border-white/12 bg-black/18 px-2 py-1 text-[10px] font-semibold tracking-[0.1em] text-[color:color-mix(in_srgb,var(--platform-text-strong)_90%,transparent)]">
              <span className="truncate">{primaryTag}</span>
            </span>
            <span className="inline-flex items-center gap-1 text-[11px] font-semibold text-[var(--platform-text-base)]">
              <span>
                {entry.visibility === 'published' ? '进入世界' : '继续创作'}
              </span>
              <ArrowRight className="h-3.5 w-3.5 shrink-0" />
            </span>
          </div>
        </div>
      </div>
    </button>
  );
}

function SaveArchiveCard({
  entry,
  onClick,
  loading = false,
}: {
  entry: ProfileSaveArchiveSummary;
  onClick: () => void;
  loading?: boolean;
}) {
  const summaryText =
    entry.summaryText || entry.subtitle || '继续推进上一次保存的故事。';
  const displayName = formatPlatformWorkDisplayName(entry.worldName);

  return (
    <button
      type="button"
      onClick={onClick}
      disabled={loading}
      className={`platform-surface platform-surface--soft platform-interactive-card relative flex min-h-[13rem] w-full overflow-hidden p-3.5 text-left sm:min-h-[12.5rem] sm:p-4 ${loading ? 'opacity-80' : ''}`}
    >
      <div className="absolute inset-0 bg-[var(--platform-card-overlay-deep)]" />
      <div className="relative z-10 flex h-full w-full flex-col gap-3">
        <div className="flex flex-wrap items-center justify-between gap-2">
          <span className="platform-pill platform-pill--cool">ARCHIVE</span>
          <span className="rounded-full border border-white/10 bg-black/18 px-2.5 py-1 text-[11px] font-medium text-[var(--platform-text-base)]">
            {loading ? '恢复中' : formatSnapshotTime(entry.lastPlayedAt)}
          </span>
        </div>
        <div className="flex min-w-0 flex-1 items-stretch gap-3 sm:gap-4">
          <div className="min-w-0 flex-1">
            <div className="line-clamp-2 break-words text-[1.15rem] font-black leading-tight text-[var(--platform-text-strong)] sm:text-xl">
              {displayName}
            </div>
            {entry.subtitle ? (
              <div className="mt-1 line-clamp-1 break-words text-sm text-[var(--platform-text-base)]">
                {entry.subtitle}
              </div>
            ) : null}
            <div className="mt-2 line-clamp-3 break-words text-xs leading-5 text-[var(--platform-text-soft)] sm:text-sm">
              {summaryText}
            </div>
            <div className="mt-4 inline-flex items-center gap-1.5 text-xs font-semibold text-zinc-200">
              <span>{loading ? '正在恢复' : '继续游玩'}</span>
              <ArrowRight className="h-3.5 w-3.5 shrink-0" />
            </div>
          </div>
          <SaveArchivePreview
            entry={entry}
            label={loading ? '恢复中' : '最近存档'}
            className="h-[7.4rem] w-[5.6rem] sm:h-[8rem] sm:w-[6.4rem]"
          />
        </div>
      </div>
    </button>
  );
}

function PlatformTabButton({
  active,
  label,
  icon: Icon,
  onClick,
  emphasized = false,
}: {
  active: boolean;
  label: string;
  icon: ComponentType<{ className?: string }>;
  onClick: () => void;
  emphasized?: boolean;
}) {
  return (
    <button
      type="button"
      onClick={onClick}
      className={`platform-bottom-nav__button ${emphasized ? 'platform-bottom-nav__button--primary' : ''} ${active ? 'platform-bottom-nav__button--active' : ''}`}
    >
      <span className="platform-bottom-nav__button-content">
        <span className="platform-bottom-nav__icon-shell">
          <Icon className="platform-bottom-nav__icon" />
        </span>
        <span className="platform-bottom-nav__label">{label}</span>
      </span>
    </button>
  );
}

function DesktopTabButton({
  active,
  label,
  icon: Icon,
  onClick,
  emphasized = false,
}: {
  active: boolean;
  label: string;
  icon: ComponentType<{ className?: string }>;
  onClick: () => void;
  emphasized?: boolean;
}) {
  return (
    <button
      type="button"
      onClick={onClick}
      className={`platform-desktop-rail__button ${emphasized ? 'platform-desktop-rail__button--primary' : ''} ${active ? 'platform-desktop-rail__button--active' : ''}`}
    >
      <span className="platform-desktop-rail__icon-shell">
        <Icon className="platform-desktop-rail__icon h-[1.1rem] w-[1.1rem]" />
      </span>
      <span className="platform-desktop-rail__label text-[11px] font-semibold tracking-[0.2em]">
        {label}
      </span>
    </button>
  );
}

function PlatformTabPanel({
  tab,
  activeTab,
  children,
}: {
  tab: PlatformHomeTab;
  activeTab: PlatformHomeTab;
  children: ReactNode;
}) {
  const active = activeTab === tab;

  // 主 Tab 保持挂载，只切换可见性，避免重页面卸载重建造成首帧闪烁。
  return (
    <section
      id={`platform-tab-panel-${tab}`}
      aria-hidden={!active}
      className={`platform-tab-panel ${active ? 'platform-tab-panel--active' : 'platform-tab-panel--hidden'}`}
    >
      {children}
    </section>
  );
}

function DesktopTrendingItem({
  entry,
  rank,
  onClick,
}: {
  entry: PlatformPublicGalleryCard;
  rank: number;
  onClick: () => void;
}) {
  const coverImage = resolvePlatformWorldCoverImage(entry);
  const displayName = formatPlatformWorkDisplayName(entry.worldName);
  const tags = buildPlatformWorldDisplayTags(entry, 2);

  return (
    <button
      type="button"
      onClick={onClick}
      className="platform-desktop-trending-item flex items-center gap-4 px-4 py-4 text-left"
    >
      <div className="relative h-[5.5rem] w-[4.3rem] shrink-0 overflow-hidden rounded-[1rem] border border-[var(--platform-subpanel-border)] bg-[rgba(255,255,255,0.66)]">
        {coverImage ? (
          <ResolvedAssetBackdrop
            src={coverImage}
            alt={entry.worldName}
            className="h-full w-full object-cover"
          />
        ) : null}
        <div className="absolute inset-0 bg-[linear-gradient(180deg,rgba(255,255,255,0.04),rgba(91,24,46,0.26))]" />
      </div>

      <div className="min-w-0 flex-1">
        <div className="flex items-center gap-2 text-[10px] tracking-[0.18em] text-[var(--platform-text-soft)]">
          <span>{`${rank}`.padStart(2, '0')}</span>
          <span className="truncate">
            {describePublicGalleryCardKind(entry)}
          </span>
        </div>
        <div className="mt-2 line-clamp-1 text-lg font-semibold text-[var(--platform-text-strong)]">
          {displayName}
        </div>
        <div className="mt-1 line-clamp-2 text-sm leading-6 text-[color:color-mix(in_srgb,var(--platform-text-base)_86%,transparent)]">
          {entry.summaryText || entry.subtitle || '等待补充世界摘要。'}
        </div>
        <div className="mt-3 flex flex-wrap gap-2">
          {tags.length > 0 ? (
            tags.map((tag, index) => (
              <span
                key={`${entry.profileId}-trend-tag-${index}-${tag}`}
                className="platform-pill platform-pill--neutral px-2.5"
              >
                {tag}
              </span>
            ))
          ) : (
            <span className="platform-pill platform-pill--neutral px-2.5">
              {isBigFishGalleryEntry(entry)
                ? '大鱼'
                : isPuzzleGalleryEntry(entry)
                  ? '拼图'
                  : describePublicGalleryCardKind(entry)}
            </span>
          )}
        </div>
      </div>

      <ChevronRight className="h-4 w-4 shrink-0 text-[var(--platform-text-soft)]" />
    </button>
  );
}

function PlatformRankingItem({
  entry,
  rank,
  metricLabel,
  metricValue,
  onClick,
}: {
  entry: PlatformPublicGalleryCard;
  rank: number;
  metricLabel: string;
  metricValue: number;
  onClick: () => void;
}) {
  const coverImage = resolvePlatformWorldCoverImage(entry);
  const displayName = formatPlatformWorkDisplayName(entry.worldName);
  const tags = buildPlatformWorldDisplayTags(entry, 2);
  const actionLabel = isPuzzleGalleryEntry(entry) ? '试玩' : '进入';

  return (
    <button
      type="button"
      onClick={onClick}
      className="platform-ranking-item w-full text-left"
    >
      <div className="platform-ranking-item__rank">{rank}</div>
      <div className="platform-ranking-item__cover">
        {coverImage ? (
          <ResolvedAssetBackdrop
            src={coverImage}
            alt={entry.worldName}
            className="h-full w-full object-cover"
          />
        ) : (
          <div className="h-full w-full bg-[radial-gradient(circle_at_24%_18%,rgba(255,255,255,0.24),transparent_30%),linear-gradient(135deg,rgba(255,118,117,0.42),rgba(89,164,255,0.34))]" />
        )}
      </div>
      <div className="min-w-0 flex-1">
        <div className="line-clamp-1 break-words text-base font-black leading-tight text-[var(--platform-text-strong)]">
          {displayName}
        </div>
        <div className="mt-1 flex min-w-0 flex-wrap items-center gap-x-1.5 gap-y-1 text-xs font-semibold text-[var(--platform-text-soft)]">
          <span className="text-[var(--platform-warm-text)]">
            {formatCompactCount(metricValue)}
          </span>
          <span>{metricLabel}</span>
          <span>·</span>
          <span>{describePublicGalleryCardKind(entry)}</span>
        </div>
        <div className="platform-ranking-item__tags">
          {tags.map((tag, index) => (
            <span key={`${entry.profileId}-ranking-tag-${index}-${tag}`}>
              {tag}
            </span>
          ))}
        </div>
      </div>
      <span className="platform-ranking-item__action">{actionLabel}</span>
    </button>
  );
}

function PlatformCategoryGameItem({
  entry,
  categoryTag,
  onClick,
}: {
  entry: PlatformPublicGalleryCard;
  categoryTag: string;
  onClick: () => void;
}) {
  const coverImage = resolvePlatformWorldCoverImage(entry);
  const displayName = formatPlatformWorkDisplayName(entry.worldName);
  const tags = buildPlatformWorldDisplayTags(entry, 2);
  const metric = getPlatformCategoryPrimaryMetric(entry);
  const metaParts = [
    describePublicGalleryCardKind(entry),
    ...tags.filter((tag) => tag !== categoryTag),
  ].slice(0, 3);
  const actionLabel = isPuzzleGalleryEntry(entry) ? '试玩' : '进入';
  const summaryText =
    entry.summaryText || entry.subtitle || `${displayName} 正在等待摘要。`;

  return (
    <button
      type="button"
      onClick={onClick}
      aria-label={`${entry.worldName}，${actionLabel}`}
      className="platform-category-game-item"
    >
      <div className="platform-category-game-item__cover">
        {coverImage ? (
          <ResolvedAssetBackdrop
            src={coverImage}
            alt={entry.worldName}
            className="h-full w-full object-cover"
          />
        ) : (
          <div className="h-full w-full bg-[radial-gradient(circle_at_24%_18%,rgba(255,255,255,0.26),transparent_32%),linear-gradient(135deg,rgba(255,118,117,0.44),rgba(89,164,255,0.36))]" />
        )}
      </div>

      <div className="platform-category-game-item__body">
        <div className="platform-category-game-item__title-row">
          <span className="platform-category-game-item__title">
            {displayName}
          </span>
          <span className="platform-category-game-item__badge">公开</span>
        </div>

        <div className="platform-category-game-item__meta">
          <span className="platform-category-game-item__metric">
            <Star className="h-3.5 w-3.5 fill-current" />
            <span>{formatCompactCount(metric.value)}</span>
          </span>
          <span>{metric.label}</span>
          {metaParts.length > 0 ? <span>{metaParts.join(' · ')}</span> : null}
        </div>

        <div className="platform-category-game-item__summary">
          {summaryText}
        </div>
      </div>

      <span className="platform-category-game-item__action">{actionLabel}</span>
    </button>
  );
}

function buildPublicCategoryGroups(
  featuredEntries: PlatformPublicGalleryCard[],
  latestEntries: PlatformPublicGalleryCard[],
) {
  const publicEntryMap = new Map<string, PlatformPublicGalleryCard>();

  [...featuredEntries, ...latestEntries].forEach((entry) => {
    publicEntryMap.set(buildPublicGalleryCardKey(entry), entry);
  });

  const categoryMap = new Map<string, PlatformPublicGalleryCard[]>();
  Array.from(publicEntryMap.values()).forEach((entry) => {
    const tags = buildPlatformWorldDisplayTags(entry, 3);
    const normalizedTags = tags.length > 0 ? tags : ['回响'];

    normalizedTags.forEach((tag) => {
      const entries = categoryMap.get(tag) ?? [];
      entries.push(entry);
      categoryMap.set(tag, entries);
    });
  });

  return Array.from(categoryMap.entries())
    .map(([tag, entries]) => ({ tag, entries }))
    .sort((left, right) => {
      if (right.entries.length !== left.entries.length) {
        return right.entries.length - left.entries.length;
      }

      return left.tag.localeCompare(right.tag, 'zh-CN');
    });
}

function getPlatformPublicEntries(
  featuredEntries: PlatformPublicGalleryCard[],
  latestEntries: PlatformPublicGalleryCard[],
) {
  const entryMap = new Map<string, PlatformPublicGalleryCard>();

  [...featuredEntries, ...latestEntries].forEach((entry) => {
    entryMap.set(buildPublicGalleryCardKey(entry), entry);
  });

  return Array.from(entryMap.values());
}

function buildPublicGalleryCardKey(entry: PlatformPublicGalleryCard) {
  const kind = isBigFishGalleryEntry(entry)
    ? 'big-fish'
    : isPuzzleGalleryEntry(entry)
      ? 'puzzle'
      : 'rpg';
  return `${kind}:${entry.ownerUserId}:${entry.profileId}`;
}

function describePublicGalleryCardKind(entry: PlatformPublicGalleryCard) {
  const kind = isBigFishGalleryEntry(entry)
    ? '大鱼'
    : isPuzzleGalleryEntry(entry)
      ? '拼图'
      : describePlatformThemeLabel(entry.themeMode);
  return formatPlatformWorkDisplayTag(kind);
}

function getPlatformWorldLikeCount(entry: PlatformWorldCardLike) {
  return Math.max(0, Math.round(entry.likeCount ?? 0));
}

function getPlatformWorldPlayCount(entry: PlatformWorldCardLike) {
  return Math.max(
    0,
    Math.round(('playCount' in entry && entry.playCount) || 0),
  );
}

function getPlatformWorldRemixCount(entry: PlatformWorldCardLike) {
  return Math.max(
    0,
    Math.round(('remixCount' in entry && entry.remixCount) || 0),
  );
}

function getPlatformWorldRecentPlayCount(entry: PlatformWorldCardLike) {
  return Math.max(
    0,
    Math.round(('recentPlayCount7d' in entry && entry.recentPlayCount7d) || 0),
  );
}

function getPlatformWorldTimestamp(entry: PlatformWorldCardLike) {
  const rawTime = entry.publishedAt ?? entry.updatedAt;
  return parsePlatformEntryTimestamp(rawTime);
}

// 首页“今日游戏”只看作品首次发布时间，按玩家浏览器本地自然日判断。
function parsePlatformEntryTimestamp(value: string | null | undefined) {
  if (!value) {
    return 0;
  }

  const normalized = value.trim();
  const numericTimestamp = normalized.match(/^(-?\d+(?:\.\d+)?)(?:Z)?$/u);
  if (numericTimestamp?.[1]) {
    const rawTimestamp = Number(numericTimestamp[1]);
    if (Number.isFinite(rawTimestamp)) {
      const absoluteTimestamp = Math.abs(rawTimestamp);
      const timestampMs =
        absoluteTimestamp >= 1_000_000_000_000_000
          ? rawTimestamp / 1000
          : absoluteTimestamp >= 1_000_000_000_000
            ? rawTimestamp
            : absoluteTimestamp >= 1_000_000_000
              ? rawTimestamp * 1000
              : Number.NaN;
      return Number.isNaN(timestampMs) ? 0 : timestampMs;
    }
  }

  const timestamp = new Date(normalized).getTime();
  return Number.isNaN(timestamp) ? 0 : timestamp;
}

function isSameLocalCalendarDay(left: Date, right: Date) {
  return (
    left.getFullYear() === right.getFullYear() &&
    left.getMonth() === right.getMonth() &&
    left.getDate() === right.getDate()
  );
}

function isPublishedToday(entry: PlatformPublicGalleryCard, now = new Date()) {
  const publishedAtTimestamp = parsePlatformEntryTimestamp(entry.publishedAt);
  if (publishedAtTimestamp <= 0) {
    return false;
  }

  const publishedAt = new Date(publishedAtTimestamp);
  return isSameLocalCalendarDay(publishedAt, now);
}

function filterTodayPublishedEntries(entries: PlatformPublicGalleryCard[]) {
  const now = new Date();
  return entries.filter((entry) => isPublishedToday(entry, now));
}

function sortEntriesByMetric(
  entries: PlatformPublicGalleryCard[],
  getMetric: (entry: PlatformPublicGalleryCard) => number,
) {
  return [...entries].sort((left, right) => {
    const metricDiff = getMetric(right) - getMetric(left);
    if (metricDiff !== 0) {
      return metricDiff;
    }

    return getPlatformWorldTimestamp(right) - getPlatformWorldTimestamp(left);
  });
}

function buildPlatformRankingEntries(
  entries: PlatformPublicGalleryCard[],
  tab: PlatformRankingTab,
) {
  if (tab === 'hot') {
    return sortEntriesByMetric(entries, getPlatformWorldPlayCount);
  }

  if (tab === 'remix') {
    return sortEntriesByMetric(entries, getPlatformWorldRemixCount);
  }

  if (tab === 'like') {
    return sortEntriesByMetric(entries, getPlatformWorldLikeCount);
  }

  return sortEntriesByMetric(entries, getPlatformWorldRecentPlayCount);
}

function getPlatformRankingMetricValue(
  entry: PlatformPublicGalleryCard,
  tab: PlatformRankingTab,
) {
  if (tab === 'remix') {
    return getPlatformWorldRemixCount(entry);
  }

  if (tab === 'like') {
    return getPlatformWorldLikeCount(entry);
  }

  if (tab === 'new') {
    return getPlatformWorldRecentPlayCount(entry);
  }

  return getPlatformWorldPlayCount(entry);
}

function getPlatformCategoryCompositeScore(entry: PlatformPublicGalleryCard) {
  // 分类频道只使用公开读模型已经返回的指标做前端排序，不在 UI 层伪造评分数据。
  return (
    getPlatformWorldPlayCount(entry) +
    getPlatformWorldRemixCount(entry) +
    getPlatformWorldLikeCount(entry) +
    getPlatformWorldRecentPlayCount(entry)
  );
}

function getPlatformCategoryPrimaryMetric(entry: PlatformPublicGalleryCard) {
  const likeCount = getPlatformWorldLikeCount(entry);
  if (likeCount > 0) {
    return { label: '点赞', value: likeCount };
  }

  const recentPlayCount = getPlatformWorldRecentPlayCount(entry);
  if (recentPlayCount > 0) {
    return { label: '近7日', value: recentPlayCount };
  }

  return { label: '游玩', value: getPlatformWorldPlayCount(entry) };
}

function formatCompactCount(value: number) {
  const normalizedValue = Math.max(0, Math.round(value));
  if (normalizedValue >= 100000000) {
    return `${(normalizedValue / 100000000).toFixed(1)}亿`;
  }
  if (normalizedValue >= 10000) {
    return `${(normalizedValue / 10000).toFixed(1)}万`;
  }
  return `${normalizedValue}`;
}

function formatSnapshotTime(value: string | null | undefined) {
  if (!value) {
    return '刚刚保存';
  }

  const date = new Date(value);
  if (Number.isNaN(date.getTime())) {
    return value;
  }

  return date.toLocaleString('zh-CN', {
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
  });
}

function formatCompactPlayTime(playTimeMs: number) {
  const totalMinutes = Math.max(0, Math.floor(playTimeMs / 60000));
  const days = totalMinutes / 1440;

  if (days >= 10) {
    return `${Math.floor(days)}天`;
  }
  if (days >= 1) {
    return `${days.toFixed(days >= 3 ? 0 : 1)}天`;
  }

  const hours = totalMinutes / 60;
  if (hours >= 1) {
    return `${hours.toFixed(hours >= 10 ? 0 : 1)}小时`;
  }

  return `${Math.max(0, totalMinutes)}分`;
}

// “总游戏时长”固定使用小时，避免短时长切到分钟或长时长切到天。
function formatTotalPlayTimeHours(playTimeMs: number) {
  const roundedHours = Math.max(0, Math.round(playTimeMs / 360000) / 10);

  return `${roundedHours.toLocaleString('zh-CN', {
    maximumFractionDigits: 1,
  })}小时`;
}

function formatDashboardCount(value: number) {
  const normalizedValue = Math.max(0, Math.round(value));
  if (normalizedValue >= 100000000) {
    return `${(normalizedValue / 100000000).toFixed(1)}亿`;
  }
  if (normalizedValue >= 10000) {
    return `${(normalizedValue / 10000).toFixed(1)}万`;
  }

  return normalizedValue.toLocaleString('zh-CN');
}

function formatDashboardUpdatedAt(value: string | null | undefined) {
  if (!value) {
    return '暂无更新记录';
  }

  const date = new Date(value);
  if (Number.isNaN(date.getTime())) {
    return value;
  }

  return date.toLocaleString('zh-CN', {
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
  });
}

function formatPlayedWorkType(value: string | null | undefined) {
  const normalizedValue = (value ?? '').toLowerCase();
  if (normalizedValue === 'puzzle') {
    return '拼图';
  }
  if (normalizedValue === 'big_fish' || normalizedValue === 'big-fish') {
    return '大鱼';
  }
  return 'RPG';
}

function formatPlayedWorkId(work: ProfilePlayedWorkSummary) {
  return work.profileId?.trim() || work.worldKey;
}

function buildPublicUserCode(user: AuthUser | null | undefined) {
  if (user?.publicUserCode?.trim()) {
    return user.publicUserCode.trim();
  }

  const raw =
    user?.id.replace(/[^a-zA-Z0-9]/gu, '').toUpperCase() ||
    user?.username.replace(/[^a-zA-Z0-9]/gu, '').toUpperCase() ||
    '00000000';

  return `SY-${raw.slice(-8).padStart(8, '0')}`;
}

function getUserAvatarLabel(user: AuthUser | null | undefined) {
  return (user?.displayName || user?.username || '叙')
    .slice(0, 1)
    .toUpperCase();
}

function validateProfileDisplayName(value: string) {
  const normalized = value.trim();
  if (!normalized) {
    return '请输入昵称';
  }
  const length = Array.from(normalized).length;
  if (length < 2 || length > 20) {
    return '昵称需要 2 到 20 位';
  }
  if (!/^[\u4e00-\u9fffa-zA-Z0-9_]+$/u.test(normalized)) {
    return '昵称仅支持中文、英文、数字和下划线';
  }

  return null;
}

function readImageIntrinsicSize(src: string) {
  return new Promise<{ width: number; height: number }>((resolve, reject) => {
    const image = new Image();
    image.onload = () => {
      resolve({
        width: image.naturalWidth,
        height: image.naturalHeight,
      });
    };
    image.onerror = () => reject(new Error('图片读取失败'));
    image.src = src;
  });
}

function loadAvatarFile(file: File) {
  return new Promise<string>((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => {
      if (typeof reader.result !== 'string') {
        reject(new Error('图片读取失败'));
        return;
      }
      resolve(reader.result);
    };
    reader.onerror = () => reject(new Error('图片读取失败'));
    reader.readAsDataURL(file);
  });
}

function cropAvatarImage(params: {
  source: string;
  cropX: number;
  cropY: number;
  cropSize: number;
}) {
  return new Promise<string>((resolve, reject) => {
    const image = new Image();
    image.onload = () => {
      const canvas = document.createElement('canvas');
      canvas.width = AVATAR_OUTPUT_SIZE;
      canvas.height = AVATAR_OUTPUT_SIZE;
      const context = canvas.getContext('2d');
      if (!context) {
        reject(new Error('头像裁剪失败'));
        return;
      }

      context.drawImage(
        image,
        params.cropX,
        params.cropY,
        params.cropSize,
        params.cropSize,
        0,
        0,
        AVATAR_OUTPUT_SIZE,
        AVATAR_OUTPUT_SIZE,
      );
      resolve(canvas.toDataURL('image/png'));
    };
    image.onerror = () => reject(new Error('头像裁剪失败'));
    image.src = params.source;
  });
}

function ProfileStatCard({
  cardKey,
  label,
  value,
  onClick,
  icon,
}: {
  cardKey: ProfileDashboardCardKey;
  label: string;
  value: string;
  onClick?: ((cardKey: ProfileDashboardCardKey) => void) | null;
  icon: ComponentType<{ className?: string }>;
}) {
  const Icon = icon;

  return (
    <button
      type="button"
      onClick={onClick ? () => onClick(cardKey) : undefined}
      className="platform-subpanel rounded-[1.35rem] px-4 py-3 text-left transition hover:border-[var(--platform-surface-hover-border)] hover:bg-[var(--platform-button-secondary-fill)]"
    >
      <div className="flex items-center gap-2 text-[var(--platform-text-soft)]">
        <Icon className="h-4 w-4" />
        <span className="text-[11px] tracking-[0.16em]">{label}</span>
      </div>
      <div className="mt-3 text-lg font-black text-[var(--platform-text-strong)]">
        {value}
      </div>
    </button>
  );
}

function ProfileStatCardSkeleton() {
  return (
    <div className="platform-subpanel rounded-[1.35rem] px-4 py-3">
      <div className="h-4 w-20 animate-pulse rounded-full bg-[var(--platform-subpanel-border)]" />
      <div className="mt-3 h-7 w-16 animate-pulse rounded-full bg-[var(--platform-line-soft)]" />
    </div>
  );
}

function ProfileShortcutButton({
  label,
  icon,
  onClick,
}: {
  label: string;
  icon: ComponentType<{ className?: string }>;
  onClick?: (() => void) | null;
}) {
  const Icon = icon;

  return (
    <button
      type="button"
      onClick={onClick ?? undefined}
      className="platform-subpanel flex min-h-[5.25rem] flex-col items-center justify-center gap-2 rounded-[1.2rem] px-3 py-3 text-center transition hover:border-[var(--platform-surface-hover-border)] hover:bg-[var(--platform-button-secondary-fill)]"
    >
      <div className="platform-profile-chip flex h-10 w-10 items-center justify-center rounded-full">
        <Icon className="h-[1.125rem] w-[1.125rem]" />
      </div>
      <div className="text-sm font-semibold text-[var(--platform-text-strong)]">
        {label}
      </div>
    </button>
  );
}

function ProfileNicknameModal({
  value,
  error,
  isSaving,
  onChange,
  onClose,
  onSubmit,
}: {
  value: string;
  error: string | null;
  isSaving: boolean;
  onChange: (value: string) => void;
  onClose: () => void;
  onSubmit: () => void;
}) {
  return (
    <div className="platform-modal-backdrop fixed inset-0 z-[80] flex items-center justify-center px-4 py-6">
      <div className="platform-recharge-modal w-full max-w-sm overflow-hidden rounded-[1.4rem]">
        <div className="flex items-center justify-between border-b border-white/10 px-5 py-4">
          <div className="text-base font-black">修改昵称</div>
          <button
            type="button"
            aria-label="关闭昵称修改"
            onClick={onClose}
            className="platform-profile-icon-button flex h-8 w-8 items-center justify-center rounded-full"
          >
            ×
          </button>
        </div>
        <div className="px-5 py-5">
          <label className="block">
            <span className="sr-only">新昵称</span>
            <input
              autoFocus
              value={value}
              onChange={(event) => onChange(event.target.value)}
              onKeyDown={(event) => {
                if (event.key === 'Enter') {
                  event.preventDefault();
                  onSubmit();
                }
              }}
              maxLength={20}
              className="w-full rounded-2xl border border-white/12 bg-white/10 px-4 py-3 text-base font-semibold text-[var(--platform-text-strong)] outline-none transition focus:border-[var(--platform-surface-hover-border)]"
              placeholder="输入新昵称"
            />
          </label>
          {error ? (
            <div className="mt-3 rounded-2xl border border-rose-400/25 bg-rose-500/10 px-3 py-2 text-sm text-rose-600">
              {error}
            </div>
          ) : null}
          <div className="mt-5 grid grid-cols-2 gap-3">
            <button
              type="button"
              onClick={onClose}
              className="platform-button platform-button--secondary justify-center"
            >
              取消
            </button>
            <button
              type="button"
              onClick={onSubmit}
              disabled={isSaving}
              className="platform-button platform-button--primary justify-center disabled:cursor-not-allowed disabled:opacity-60"
            >
              {isSaving ? '保存中' : '保存'}
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

function ProfileAvatarCropModal({
  source,
  imageSize,
  scale,
  cropX,
  cropY,
  error,
  isSaving,
  onScaleChange,
  onCropChange,
  onClose,
  onSubmit,
}: {
  source: string;
  imageSize: { width: number; height: number };
  scale: number;
  cropX: number;
  cropY: number;
  error: string | null;
  isSaving: boolean;
  onScaleChange: (value: number) => void;
  onCropChange: (nextCrop: { x: number; y: number }) => void;
  onClose: () => void;
  onSubmit: () => void;
}) {
  const previewRef = useRef<HTMLDivElement | null>(null);
  const dragStartRef = useRef<{
    pointerId: number;
    clientX: number;
    clientY: number;
    cropX: number;
    cropY: number;
  } | null>(null);
  const [isDragging, setIsDragging] = useState(false);
  const cropSize = Math.min(imageSize.width, imageSize.height) / scale;
  const maxCropX = Math.max(0, imageSize.width - cropSize);
  const maxCropY = Math.max(0, imageSize.height - cropSize);
  const backgroundSize = `${(imageSize.width / cropSize) * 100}% ${(imageSize.height / cropSize) * 100}%`;
  const backgroundPosition = `${maxCropX > 0 ? (cropX / maxCropX) * 100 : 50}% ${maxCropY > 0 ? (cropY / maxCropY) * 100 : 50}%`;
  const updateDragCrop = (event: PointerEvent<HTMLDivElement>) => {
    const dragStart = dragStartRef.current;
    const preview = previewRef.current;
    if (!dragStart || !preview || event.pointerId !== dragStart.pointerId) {
      return;
    }

    const rect = preview.getBoundingClientRect();
    const sourcePixelsPerPreviewPixel = cropSize / Math.max(1, rect.width);
    onCropChange({
      x:
        dragStart.cropX -
        (event.clientX - dragStart.clientX) * sourcePixelsPerPreviewPixel,
      y:
        dragStart.cropY -
        (event.clientY - dragStart.clientY) * sourcePixelsPerPreviewPixel,
    });
  };
  const stopDragging = (event: PointerEvent<HTMLDivElement>) => {
    if (dragStartRef.current?.pointerId === event.pointerId) {
      dragStartRef.current = null;
      setIsDragging(false);
      event.currentTarget.releasePointerCapture(event.pointerId);
    }
  };

  return (
    <div className="platform-modal-backdrop fixed inset-0 z-[80] flex items-center justify-center px-4 py-6">
      <div className="platform-recharge-modal w-full max-w-sm overflow-hidden rounded-[1.4rem]">
        <div className="flex items-center justify-between border-b border-white/10 px-5 py-4">
          <div className="text-base font-black">裁剪头像</div>
          <button
            type="button"
            aria-label="关闭头像裁剪"
            onClick={onClose}
            className="platform-profile-icon-button flex h-8 w-8 items-center justify-center rounded-full"
          >
            ×
          </button>
        </div>
        <div className="px-5 py-5">
          <div
            ref={previewRef}
            className="mx-auto aspect-square w-full max-w-[16rem] overflow-hidden rounded-[1.4rem] border border-white/12 bg-cover bg-center"
            style={{
              backgroundImage: `url("${source}")`,
              backgroundSize,
              backgroundPosition,
              cursor: isDragging ? 'grabbing' : 'grab',
              touchAction: 'none',
            }}
            role="img"
            aria-label="头像裁剪预览"
            onPointerDown={(event) => {
              dragStartRef.current = {
                pointerId: event.pointerId,
                clientX: event.clientX,
                clientY: event.clientY,
                cropX,
                cropY,
              };
              setIsDragging(true);
              event.currentTarget.setPointerCapture(event.pointerId);
            }}
            onPointerMove={updateDragCrop}
            onPointerUp={stopDragging}
            onPointerCancel={stopDragging}
          />
          <div className="mt-5 space-y-4">
            <label className="block">
              <span className="mb-2 block text-xs font-semibold text-[var(--platform-text-soft)]">
                缩放
              </span>
              <input
                type="range"
                min="1"
                max="3"
                step="0.01"
                value={scale}
                onChange={(event) => onScaleChange(Number(event.target.value))}
                className="w-full"
              />
            </label>
            <div className="grid grid-cols-2 gap-3">
              <label className="block">
                <span className="mb-2 block text-xs font-semibold text-[var(--platform-text-soft)]">
                  横向
                </span>
                <input
                  type="range"
                  min="0"
                  max={maxCropX}
                  step="1"
                  value={Math.min(cropX, maxCropX)}
                  onChange={(event) =>
                    onCropChange({ x: Number(event.target.value), y: cropY })
                  }
                  className="w-full"
                />
              </label>
              <label className="block">
                <span className="mb-2 block text-xs font-semibold text-[var(--platform-text-soft)]">
                  纵向
                </span>
                <input
                  type="range"
                  min="0"
                  max={maxCropY}
                  step="1"
                  value={Math.min(cropY, maxCropY)}
                  onChange={(event) =>
                    onCropChange({ x: cropX, y: Number(event.target.value) })
                  }
                  className="w-full"
                />
              </label>
            </div>
          </div>
          {error ? (
            <div className="mt-4 rounded-2xl border border-rose-400/25 bg-rose-500/10 px-3 py-2 text-sm text-rose-600">
              {error}
            </div>
          ) : null}
          <div className="mt-5 grid grid-cols-2 gap-3">
            <button
              type="button"
              onClick={onClose}
              className="platform-button platform-button--secondary justify-center"
            >
              取消
            </button>
            <button
              type="button"
              onClick={onSubmit}
              disabled={isSaving}
              className="platform-button platform-button--primary justify-center disabled:cursor-not-allowed disabled:opacity-60"
            >
              {isSaving ? '上传中' : '上传'}
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

const WALLET_LEDGER_SOURCE_LABELS: Record<string, string> = {
  points_recharge: '陶泥币充值',
  invite_inviter_reward: '邀请奖励',
  invite_invitee_reward: '填写邀请码奖励',
  snapshot_sync: '账户同步',
  asset_operation_consume: '资产操作消耗',
  asset_operation_refund: '资产操作退回',
  redeem_code_reward: '兑换码奖励',
};

function formatWalletLedgerAmount(amountDelta: number) {
  return amountDelta > 0 ? `+${amountDelta}` : `${amountDelta}`;
}

function WalletLedgerModal({
  ledger,
  fallbackBalance,
  isLoading,
  error,
  onClose,
  onRetry,
}: {
  ledger: ProfileWalletLedgerResponse | null;
  fallbackBalance: number;
  isLoading: boolean;
  error: string | null;
  onClose: () => void;
  onRetry: () => void;
}) {
  const entries = ledger?.entries ?? [];
  const balance = entries[0]?.balanceAfter ?? fallbackBalance;

  return (
    <div className="fixed inset-0 z-[80] flex items-center justify-center bg-black/48 px-3 py-5">
      <div className="relative max-h-[min(92vh,42rem)] w-full max-w-[30rem] overflow-hidden rounded-[1.35rem] bg-[linear-gradient(180deg,#fff7f8_0%,#ffffff_38%,#f8fafc_100%)] text-zinc-950 shadow-2xl">
        <button
          type="button"
          onClick={onClose}
          className="absolute right-3 top-3 z-10 flex h-8 w-8 items-center justify-center rounded-full bg-white/78 text-[#ff4056] shadow-sm"
          aria-label="关闭陶泥币账单"
        >
          ×
        </button>
        <div className="max-h-[min(92vh,42rem)] overflow-y-auto px-4 pb-5 pt-4 sm:px-5">
          <div className="pr-10">
            <div className="text-[10px] font-black tracking-[0.22em] text-[#ff4056]">
              LEDGER
            </div>
            <div className="mt-1 text-2xl font-black">陶泥币账单</div>
            <div className="mt-3 inline-flex items-center gap-2 rounded-full border border-rose-100 bg-white/70 px-3 py-1.5 text-xs font-bold text-zinc-600">
              <Coins className="h-3.5 w-3.5 text-[#ff4056]" />
              <span>{balance}陶泥币</span>
            </div>
          </div>

          {error ? (
            <div className="mt-4 rounded-xl border border-rose-200 bg-rose-50 px-3 py-3 text-sm text-rose-700">
              <div>{error}</div>
              <button
                type="button"
                onClick={onRetry}
                className="mt-3 rounded-full bg-[#ff4056] px-4 py-2 text-xs font-black text-white"
              >
                重新加载
              </button>
            </div>
          ) : isLoading ? (
            <div className="mt-5 space-y-3">
              {Array.from({ length: 5 }).map((_, index) => (
                <div
                  key={index}
                  className="h-16 animate-pulse rounded-xl bg-zinc-100"
                />
              ))}
            </div>
          ) : entries.length === 0 ? (
            <div className="mt-5 rounded-xl border border-zinc-200 bg-white px-4 py-8 text-center text-sm font-semibold text-zinc-500">
              暂无账单记录
            </div>
          ) : (
            <div className="mt-5 space-y-2.5">
              {entries.map((entry) => {
                const isIncome = entry.amountDelta > 0;
                const label =
                  WALLET_LEDGER_SOURCE_LABELS[entry.sourceType] ??
                  entry.sourceType;

                return (
                  <div
                    key={entry.id}
                    className="flex items-center justify-between gap-3 rounded-xl border border-zinc-200 bg-white px-3 py-3 shadow-sm"
                  >
                    <div className="min-w-0">
                      <div className="truncate text-sm font-black text-zinc-900">
                        {label}
                      </div>
                      <div className="mt-1 text-xs font-semibold text-zinc-500">
                        {formatPlatformWorldTime(entry.createdAt)}
                      </div>
                    </div>
                    <div className="shrink-0 text-right">
                      <div
                        className={`text-base font-black ${
                          isIncome ? 'text-emerald-600' : 'text-rose-500'
                        }`}
                      >
                        {formatWalletLedgerAmount(entry.amountDelta)}
                      </div>
                      <div className="mt-1 text-[11px] font-semibold text-zinc-400">
                        余额 {entry.balanceAfter}
                      </div>
                    </div>
                  </div>
                );
              })}
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

function RewardCodeRedeemModal({
  value,
  isSubmitting,
  error,
  success,
  onChange,
  onSubmit,
  onClose,
}: {
  value: string;
  isSubmitting: boolean;
  error: string | null;
  success: string | null;
  onChange: (value: string) => void;
  onSubmit: () => void;
  onClose: () => void;
}) {
  return (
    <div className="platform-modal-backdrop fixed inset-0 z-[80] flex items-center justify-center px-4 py-6">
      <div className="platform-recharge-modal w-full max-w-sm overflow-hidden rounded-[1.4rem]">
        <div className="flex items-center justify-between border-b border-white/10 px-5 py-4">
          <div className="text-base font-black">兑换码</div>
          <button
            type="button"
            aria-label="关闭兑换码"
            onClick={onClose}
            className="platform-modal-close flex h-9 w-9 items-center justify-center rounded-full"
          >
            ×
          </button>
        </div>
        <div className="space-y-3 px-5 py-5">
          <input
            value={value}
            onChange={(event) => onChange(event.target.value)}
            onKeyDown={(event) => {
              if (event.key === 'Enter') {
                onSubmit();
              }
            }}
            className="platform-profile-input w-full rounded-2xl px-4 py-3 text-sm font-semibold uppercase tracking-normal"
            placeholder="输入兑换码"
            autoFocus
          />
          <button
            type="button"
            onClick={onSubmit}
            disabled={isSubmitting || !value.trim()}
            className="platform-primary-button w-full rounded-2xl px-4 py-3 text-sm font-black disabled:cursor-not-allowed disabled:opacity-50"
          >
            {isSubmitting ? '兑换中' : '兑换'}
          </button>
          {error ? (
            <div className="platform-profile-error rounded-2xl px-3 py-2 text-xs font-semibold">
              {error}
            </div>
          ) : null}
          {success ? (
            <div className="platform-profile-success rounded-2xl px-3 py-2 text-xs font-semibold">
              {success}
            </div>
          ) : null}
        </div>
      </div>
    </div>
  );
}

function ProfileReferralModal({
  panel,
  center,
  inviteCodeInput,
  isLoading,
  isSubmitting,
  error,
  success,
  onClose,
  onInputChange,
  onCopyInvite,
  onSubmitRedeem,
}: {
  panel: ProfilePopupPanel;
  center: ProfileReferralInviteCenterResponse | null;
  inviteCodeInput: string;
  isLoading: boolean;
  isSubmitting: boolean;
  error: string | null;
  success: string | null;
  onClose: () => void;
  onInputChange: (value: string) => void;
  onCopyInvite: () => void;
  onSubmitRedeem: () => void;
}) {
  const title =
    panel === 'invite'
      ? '邀请好友'
      : panel === 'redeem'
        ? '填邀请码'
        : '玩家社区';

  return (
    <div className="fixed inset-0 z-[80] flex items-center justify-center bg-black/42 px-3 py-5">
      <div className="relative w-full max-w-[24rem] overflow-hidden rounded-[1.35rem] bg-white text-zinc-950 shadow-2xl">
        <button
          type="button"
          onClick={onClose}
          className="absolute right-3 top-2 z-10 flex h-8 w-8 items-center justify-center rounded-full text-[#ff4056]"
          aria-label={`关闭${title}`}
        >
          ×
        </button>
        <div className="px-5 pb-5 pt-4">
          <div className="text-center text-xl font-black">{title}</div>

          {panel === 'community' ? (
            <div className="mt-5 grid grid-cols-2 gap-3">
              {COMMUNITY_QR_CODES.map((qrCode) => (
                <div
                  key={qrCode.label}
                  className="rounded-xl border border-zinc-200 bg-zinc-50 p-2.5 text-center"
                >
                  <div className="aspect-square overflow-hidden rounded-lg border border-zinc-200 bg-white p-1.5">
                    <img
                      src={qrCode.src}
                      alt={qrCode.alt}
                      className="h-full w-full object-contain"
                      loading="lazy"
                      decoding="async"
                    />
                  </div>
                  <div className="mt-2 text-sm font-bold text-zinc-700">
                    {qrCode.label}
                  </div>
                </div>
              ))}
            </div>
          ) : isLoading ? (
            <div className="mt-5 space-y-3">
              <div className="h-20 animate-pulse rounded-xl bg-zinc-100" />
              <div className="h-10 animate-pulse rounded-xl bg-zinc-100" />
            </div>
          ) : panel === 'invite' ? (
            <div className="mt-5 space-y-3">
              <div className="rounded-xl bg-zinc-50 px-4 py-4 text-center">
                <div className="text-[11px] font-bold text-zinc-500">
                  邀请码
                </div>
                <div className="mt-1 text-3xl font-black tracking-[0.16em] text-[#ff4056]">
                  {center?.inviteCode ?? '--------'}
                </div>
              </div>
              <button
                type="button"
                onClick={onCopyInvite}
                disabled={!center?.inviteCode}
                className="flex w-full items-center justify-center gap-2 rounded-xl bg-[#ff4056] px-4 py-3 text-sm font-black text-white disabled:opacity-60"
              >
                <Copy className="h-4 w-4" />
                复制邀请
              </button>
              <div className="grid grid-cols-3 gap-2 text-center text-xs text-zinc-500">
                <div className="rounded-lg bg-zinc-50 px-2 py-2">
                  <div className="font-black text-zinc-900">
                    {center?.invitedCount ?? 0}
                  </div>
                  邀请
                </div>
                <div className="rounded-lg bg-zinc-50 px-2 py-2">
                  <div className="font-black text-zinc-900">
                    {center?.rewardedInviteCount ?? 0}
                  </div>
                  已奖
                </div>
                <div className="rounded-lg bg-zinc-50 px-2 py-2">
                  <div className="font-black text-zinc-900">
                    {center?.todayInviterRewardRemaining ?? 0}
                  </div>
                  今日
                </div>
              </div>
            </div>
          ) : (
            <div className="mt-5 space-y-3">
              {center?.hasRedeemedCode ? (
                <div className="rounded-xl bg-emerald-50 px-4 py-4 text-center text-sm font-bold text-emerald-700">
                  已填写邀请码
                </div>
              ) : (
                <>
                  <input
                    value={inviteCodeInput}
                    onChange={(event) => onInputChange(event.target.value)}
                    placeholder="输入邀请码"
                    className="w-full rounded-xl border border-zinc-200 bg-zinc-50 px-4 py-3 text-center text-base font-black tracking-[0.14em] outline-none focus:border-[#ff4056]"
                  />
                  <button
                    type="button"
                    onClick={onSubmitRedeem}
                    disabled={isSubmitting || !inviteCodeInput.trim()}
                    className="w-full rounded-xl bg-[#ff4056] px-4 py-3 text-sm font-black text-white disabled:opacity-60"
                  >
                    {isSubmitting ? '提交中' : '确认填写'}
                  </button>
                </>
              )}
            </div>
          )}

          {error ? (
            <div className="mt-4 rounded-xl border border-rose-200 bg-rose-50 px-3 py-2 text-sm text-rose-700">
              {error}
            </div>
          ) : null}
          {success ? (
            <div className="mt-4 rounded-xl border border-emerald-200 bg-emerald-50 px-3 py-2 text-sm text-emerald-700">
              {success}
            </div>
          ) : null}
        </div>
      </div>
    </div>
  );
}

function ProfilePlayedWorksModal({
  stats,
  isLoading,
  error,
  onClose,
  onOpenWork,
}: {
  stats: ProfilePlayStatsResponse | null;
  isLoading: boolean;
  error: string | null;
  onClose: () => void;
  onOpenWork?: (work: ProfilePlayedWorkSummary) => void;
}) {
  const playedWorks = stats?.playedWorks ?? [];

  return (
    <div className="fixed inset-0 z-[80] flex items-center justify-center bg-black/48 px-3 py-5">
      <div className="relative max-h-[min(92vh,42rem)] w-full max-w-[34rem] overflow-hidden rounded-[1.35rem] bg-white text-zinc-950 shadow-2xl">
        <button
          type="button"
          onClick={onClose}
          className="absolute right-3 top-3 z-10 flex h-8 w-8 items-center justify-center rounded-full bg-white/80 text-[#ff4056] shadow-sm"
          aria-label="关闭玩过作品"
        >
          ×
        </button>
        <div className="max-h-[min(92vh,42rem)] overflow-y-auto px-4 pb-5 pt-4 sm:px-5">
          <div className="pr-10">
            <div className="text-[10px] font-black tracking-[0.22em] text-[#ff4056]">
              PLAYED
            </div>
            <div className="mt-1 text-2xl font-black">玩过作品</div>
            <div className="mt-2 inline-flex items-center gap-2 rounded-full border border-rose-100 bg-rose-50 px-3 py-1.5 text-xs font-bold text-zinc-600">
              <Clock3 className="h-3.5 w-3.5 text-[#ff4056]" />
              <span>
                {formatTotalPlayTimeHours(stats?.totalPlayTimeMs ?? 0)}
              </span>
            </div>
          </div>

          {error ? (
            <div className="mt-4 rounded-xl border border-rose-200 bg-rose-50 px-3 py-2 text-sm text-rose-700">
              {error}
            </div>
          ) : null}

          {isLoading ? (
            <div className="mt-5 space-y-3">
              {Array.from({ length: 4 }).map((_, index) => (
                <div
                  key={index}
                  className="h-20 animate-pulse rounded-xl bg-zinc-100"
                />
              ))}
            </div>
          ) : playedWorks.length > 0 ? (
            <div className="mt-5 space-y-3">
              {playedWorks.map((work) => (
                <button
                  type="button"
                  key={`${work.worldKey}:${work.lastPlayedAt}`}
                  onClick={() => onOpenWork?.(work)}
                  className="w-full rounded-2xl border border-zinc-200 bg-zinc-50 px-4 py-3 text-left transition hover:border-[#ff4056] hover:bg-white"
                >
                  <div className="flex min-w-0 items-start justify-between gap-3">
                    <div className="min-w-0">
                      <div className="line-clamp-1 text-base font-black text-zinc-950">
                        {work.worldTitle}
                      </div>
                      {work.worldSubtitle ? (
                        <div className="mt-1 line-clamp-1 text-xs text-zinc-500">
                          {work.worldSubtitle}
                        </div>
                      ) : null}
                    </div>
                    <span className="shrink-0 rounded-full bg-rose-50 px-2.5 py-1 text-[11px] font-bold text-[#ff4056]">
                      {formatPlayedWorkType(work.worldType)}
                    </span>
                  </div>
                  <div className="mt-3 grid gap-2 text-xs text-zinc-500 sm:grid-cols-3">
                    <span className="truncate">
                      作品号 {formatPlayedWorkId(work)}
                    </span>
                    <span className="truncate">
                      最近 {formatSnapshotTime(work.lastPlayedAt)}
                    </span>
                    <span className="truncate">
                      时长 {formatCompactPlayTime(work.lastObservedPlayTimeMs)}
                    </span>
                  </div>
                </button>
              ))}
            </div>
          ) : (
            <div className="mt-5 rounded-xl border border-zinc-200 bg-zinc-50 px-4 py-4 text-sm text-zinc-600">
              暂无玩过作品
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

export function RpgEntryHomeView({
  activeTab,
  onTabChange,
  saveEntries,
  saveError,
  featuredEntries,
  latestEntries,
  myEntries,
  historyEntries,
  profileDashboard,
  isLoadingPlatform,
  isLoadingDashboard,
  isResumingSaveWorldKey,
  platformError,
  dashboardError,
  onResumeSave,
  onOpenCreateTypePicker,
  onOpenGalleryDetail,
  onOpenLibraryDetail,
  onDeleteLibraryEntry,
  deletingLibraryEntryId = null,
  onSearchPublicCode,
  isSearchingPublicCode = false,
  onOpenProfileDashboardCard,
  profilePlayStats = null,
  isProfilePlayStatsOpen = false,
  isProfilePlayStatsLoading = false,
  profilePlayStatsError = null,
  onCloseProfilePlayStats,
  onOpenPlayedWork,
  onRechargeSuccess,
  createTabContent,
}: RpgEntryHomeViewProps) {
  const authUi = useAuthUi();
  const [desktopSearchKeyword, setDesktopSearchKeyword] = useState('');
  const [mobileSearchKeyword, setMobileSearchKeyword] = useState('');
  const [isRewardCodeOpen, setIsRewardCodeOpen] = useState(false);
  const [rewardCodeInput, setRewardCodeInput] = useState('');
  const [isSubmittingRewardCode, setIsSubmittingRewardCode] = useState(false);
  const [rewardCodeError, setRewardCodeError] = useState<string | null>(null);
  const [rewardCodeSuccess, setRewardCodeSuccess] = useState<string | null>(
    null,
  );
  const [isWalletLedgerOpen, setIsWalletLedgerOpen] = useState(false);
  const [walletLedger, setWalletLedger] =
    useState<ProfileWalletLedgerResponse | null>(null);
  const [walletLedgerError, setWalletLedgerError] = useState<string | null>(
    null,
  );
  const [isLoadingWalletLedger, setIsLoadingWalletLedger] = useState(false);
  const [profilePopupPanel, setProfilePopupPanel] =
    useState<ProfilePopupPanel | null>(null);
  const [referralCenter, setReferralCenter] =
    useState<ProfileReferralInviteCenterResponse | null>(null);
  const [isLoadingReferral, setIsLoadingReferral] = useState(false);
  const [isSubmittingReferral, setIsSubmittingReferral] = useState(false);
  const [referralError, setReferralError] = useState<string | null>(null);
  const [referralSuccess, setReferralSuccess] = useState<string | null>(null);
  const [inviteCodeInput, setInviteCodeInput] = useState('');
  const [selectedCategoryTag, setSelectedCategoryTag] = useState<string | null>(
    null,
  );
  const [mobileHomeChannel, setMobileHomeChannel] =
    useState<MobileHomeChannel>('recommend');
  const [activeRankingTab, setActiveRankingTab] =
    useState<PlatformRankingTab>('hot');
  const [visitedTabs, setVisitedTabs] = useState<Set<PlatformHomeTab>>(
    () => new Set([activeTab]),
  );
  const [profileCopyState, setProfileCopyState] = useState<
    'idle' | 'copied' | 'failed'
  >('idle');
  const profileCopyResetTimerRef = useRef<number | null>(null);
  const avatarFileInputRef = useRef<HTMLInputElement | null>(null);
  const [isNicknameModalOpen, setIsNicknameModalOpen] = useState(false);
  const [nicknameInput, setNicknameInput] = useState('');
  const [nicknameError, setNicknameError] = useState<string | null>(null);
  const [isSavingNickname, setIsSavingNickname] = useState(false);
  const [avatarSource, setAvatarSource] = useState<string | null>(null);
  const [avatarImageSize, setAvatarImageSize] = useState<{
    width: number;
    height: number;
  } | null>(null);
  const [avatarScale, setAvatarScale] = useState(1);
  const [avatarCrop, setAvatarCrop] = useState({ x: 0, y: 0 });
  const [avatarError, setAvatarError] = useState<string | null>(null);
  const [isSavingAvatar, setIsSavingAvatar] = useState(false);
  const isAuthenticated = Boolean(authUi?.user);
  const isDesktopLayout = usePlatformDesktopLayout();
  const featuredShelf = useMemo(
    () => featuredEntries.slice(0, 6),
    [featuredEntries],
  );
  const categoryGroups = useMemo(
    () => buildPublicCategoryGroups(featuredEntries, latestEntries),
    [featuredEntries, latestEntries],
  );
  const publicEntries = useMemo(
    () => getPlatformPublicEntries(featuredEntries, latestEntries),
    [featuredEntries, latestEntries],
  );
  const activeCategoryGroup =
    categoryGroups.find((group) => group.tag === selectedCategoryTag) ??
    categoryGroups[0] ??
    null;
  const activeCategoryEntries = useMemo(() => {
    if (!activeCategoryGroup) {
      return [];
    }

    return [...activeCategoryGroup.entries].sort((left, right) => {
      const scoreDiff =
        getPlatformCategoryCompositeScore(right) -
        getPlatformCategoryCompositeScore(left);
      if (scoreDiff !== 0) {
        return scoreDiff;
      }

      return getPlatformWorldTimestamp(right) - getPlatformWorldTimestamp(left);
    });
  }, [activeCategoryGroup]);
  const visibleTabs = useMemo<PlatformHomeTab[]>(
    () =>
      isAuthenticated
        ? ['home', 'category', 'create', 'saves', 'profile']
        : ['home', 'create', 'category'],
    [isAuthenticated],
  );
  const publicUserCode = buildPublicUserCode(authUi?.user);
  const avatarLabel = getUserAvatarLabel(authUi?.user);
  const avatarUrl = authUi?.user?.avatarUrl?.trim() || null;
  const avatarCropSize = avatarImageSize
    ? Math.min(avatarImageSize.width, avatarImageSize.height) / avatarScale
    : 0;
  const remainingNarrativeCoins = profileDashboard?.walletBalance ?? 0;
  const totalPlayTime = formatTotalPlayTimeHours(
    profileDashboard?.totalPlayTimeMs ?? 0,
  );
  const playedWorkCount = profileDashboard?.playedWorldCount ?? 0;
  const tabIcons = {
    home: House,
    category: Trophy,
    create: Sparkles,
    saves: Archive,
    profile: UserRound,
  } as const;
  const tabLabels = {
    home: '首页',
    category: '排行',
    create: '创作',
    saves: '存档',
    profile: '我的',
  } as const;

  useEffect(() => {
    if (!visibleTabs.includes(activeTab)) {
      onTabChange('home');
    }
  }, [activeTab, onTabChange, visibleTabs]);

  useEffect(() => {
    setVisitedTabs((currentTabs) => {
      if (currentTabs.has(activeTab)) {
        return currentTabs;
      }

      const nextTabs = new Set(currentTabs);
      nextTabs.add(activeTab);
      return nextTabs;
    });
  }, [activeTab]);

  useEffect(
    () => () => {
      if (profileCopyResetTimerRef.current !== null) {
        window.clearTimeout(profileCopyResetTimerRef.current);
      }
    },
    [],
  );

  useEffect(() => {
    if (categoryGroups.length === 0) {
      setSelectedCategoryTag(null);
      return;
    }

    const firstCategoryGroup = categoryGroups[0];
    if (
      firstCategoryGroup &&
      !categoryGroups.some((group) => group.tag === selectedCategoryTag)
    ) {
      setSelectedCategoryTag(firstCategoryGroup.tag);
    }
  }, [categoryGroups, selectedCategoryTag]);
  const openUserSurface = () => {
    if (authUi?.user) {
      authUi.openAccountModal();
      return;
    }
    authUi?.openLoginModal();
  };
  const scheduleProfileCopyStateReset = () => {
    if (profileCopyResetTimerRef.current !== null) {
      window.clearTimeout(profileCopyResetTimerRef.current);
    }
    profileCopyResetTimerRef.current = window.setTimeout(() => {
      profileCopyResetTimerRef.current = null;
      setProfileCopyState('idle');
    }, 1400);
  };
  const copyProfilePublicUserCode = () => {
    void copyTextToClipboard(publicUserCode).then((copied) => {
      setProfileCopyState(copied ? 'copied' : 'failed');
      scheduleProfileCopyStateReset();
    });
  };
  const openNicknameModal = () => {
    const user = authUi?.user;
    if (!user) {
      authUi?.openLoginModal();
      return;
    }

    setNicknameInput(user.displayName);
    setNicknameError(null);
    setIsNicknameModalOpen(true);
  };
  const submitNickname = () => {
    if (!authUi?.user || isSavingNickname) {
      return;
    }

    const validationError = validateProfileDisplayName(nicknameInput);
    if (validationError) {
      setNicknameError(validationError);
      return;
    }

    const nextName = nicknameInput.trim();
    setIsSavingNickname(true);
    setNicknameError(null);
    void updateAuthProfile({ displayName: nextName })
      .then((nextUser) => {
        authUi.setCurrentUser(nextUser);
        setIsNicknameModalOpen(false);
      })
      .catch((error: unknown) => {
        setNicknameError(
          error instanceof Error ? error.message : '昵称保存失败',
        );
      })
      .finally(() => setIsSavingNickname(false));
  };
  const openAvatarPicker = () => {
    if (!authUi?.user) {
      authUi?.openLoginModal();
      return;
    }

    setAvatarError(null);
    avatarFileInputRef.current?.click();
  };
  const handleAvatarFileChange = (file: File | null) => {
    if (avatarFileInputRef.current) {
      avatarFileInputRef.current.value = '';
    }
    if (!file) {
      return;
    }
    if (!AVATAR_ALLOWED_TYPES.has(file.type)) {
      setAvatarError('头像仅支持 jpg、png、webp');
      return;
    }
    if (file.size > AVATAR_MAX_FILE_SIZE) {
      setAvatarError('头像图片不能超过 5MB');
      return;
    }

    setAvatarError(null);
    void loadAvatarFile(file)
      .then(async (source) => {
        const imageSize = await readImageIntrinsicSize(source);
        const cropSize = Math.min(imageSize.width, imageSize.height);
        setAvatarSource(source);
        setAvatarImageSize(imageSize);
        setAvatarScale(1);
        setAvatarCrop({
          x: Math.max(0, (imageSize.width - cropSize) / 2),
          y: Math.max(0, (imageSize.height - cropSize) / 2),
        });
      })
      .catch((error: unknown) => {
        setAvatarError(
          error instanceof Error ? error.message : '头像图片读取失败',
        );
      });
  };
  const updateAvatarScale = useCallback(
    (nextScale: number) => {
      if (!avatarImageSize) {
        return;
      }

      const normalizedScale = Math.min(3, Math.max(1, nextScale));
      const nextCropSize =
        Math.min(avatarImageSize.width, avatarImageSize.height) /
        normalizedScale;
      setAvatarScale(normalizedScale);
      setAvatarCrop((current) => ({
        x: Math.min(
          current.x,
          Math.max(0, avatarImageSize.width - nextCropSize),
        ),
        y: Math.min(
          current.y,
          Math.max(0, avatarImageSize.height - nextCropSize),
        ),
      }));
    },
    [avatarImageSize],
  );
  const updateAvatarCrop = useCallback(
    (nextCrop: { x: number; y: number }) => {
      if (!avatarImageSize || avatarCropSize <= 0) {
        return;
      }

      setAvatarCrop({
        x: Math.min(
          Math.max(0, nextCrop.x),
          Math.max(0, avatarImageSize.width - avatarCropSize),
        ),
        y: Math.min(
          Math.max(0, nextCrop.y),
          Math.max(0, avatarImageSize.height - avatarCropSize),
        ),
      });
    },
    [avatarCropSize, avatarImageSize],
  );
  const submitAvatar = () => {
    if (
      !avatarSource ||
      !avatarImageSize ||
      avatarCropSize <= 0 ||
      isSavingAvatar
    ) {
      return;
    }

    setIsSavingAvatar(true);
    setAvatarError(null);
    void cropAvatarImage({
      source: avatarSource,
      cropX: avatarCrop.x,
      cropY: avatarCrop.y,
      cropSize: avatarCropSize,
    })
      .then((avatarDataUrl) => updateAuthProfile({ avatarDataUrl }))
      .then((nextUser) => {
        authUi?.setCurrentUser(nextUser);
        setAvatarSource(null);
        setAvatarImageSize(null);
      })
      .catch((error: unknown) => {
        setAvatarError(error instanceof Error ? error.message : '头像上传失败');
      })
      .finally(() => setIsSavingAvatar(false));
  };
  const loadWalletLedger = () => {
    setWalletLedgerError(null);
    setIsLoadingWalletLedger(true);
    void getRpgProfileWalletLedger()
      .then(setWalletLedger)
      .catch((error: unknown) => {
        setWalletLedger(null);
        setWalletLedgerError(
          error instanceof Error ? error.message : '读取陶泥币账单失败',
        );
      })
      .finally(() => setIsLoadingWalletLedger(false));
  };
  const openWalletLedgerPanel = () => {
    setIsWalletLedgerOpen(true);
    loadWalletLedger();
  };
  const openProfilePopupPanel = (panel: ProfilePopupPanel) => {
    setProfilePopupPanel(panel);
    setReferralError(null);
    setReferralSuccess(null);
    if (panel === 'community') {
      return;
    }

    setIsLoadingReferral(true);
    void getRpgProfileReferralInviteCenter()
      .then(setReferralCenter)
      .catch((error: unknown) => {
        setReferralCenter(null);
        setReferralError(
          error instanceof Error ? error.message : '读取邀请码失败',
        );
      })
      .finally(() => setIsLoadingReferral(false));
  };
  const copyInviteInfo = () => {
    if (!referralCenter?.inviteCode) {
      return;
    }

    const inviteUrl =
      typeof window === 'undefined'
        ? referralCenter.inviteLinkPath
        : new URL(referralCenter.inviteLinkPath, window.location.origin).href;
    void copyTextToClipboard(`${referralCenter.inviteCode} ${inviteUrl}`).then(
      (copied) => {
        setReferralSuccess(copied ? '已复制' : '复制失败');
      },
    );
  };
  const submitReferralInviteCode = () => {
    if (isSubmittingReferral || !inviteCodeInput.trim()) {
      return;
    }

    setIsSubmittingReferral(true);
    setReferralError(null);
    setReferralSuccess(null);
    void redeemRpgProfileReferralInviteCode(inviteCodeInput)
      .then((response: RedeemProfileReferralInviteCodeResponse) => {
        setReferralCenter(response.center);
        setInviteCodeInput('');
        setReferralSuccess(
          response.inviteeRewardGranted ? '已获得30陶泥币' : '填写成功',
        );
        void onRechargeSuccess?.();
      })
      .catch((error: unknown) => {
        setReferralError(
          error instanceof Error ? error.message : '填写邀请码失败',
        );
      })
      .finally(() => setIsSubmittingReferral(false));
  };
  const openRewardCodeModal = () => {
    setIsRewardCodeOpen(true);
    setRewardCodeError(null);
    setRewardCodeSuccess(null);
  };
  const submitRewardCode = () => {
    if (isSubmittingRewardCode || !rewardCodeInput.trim()) {
      return;
    }

    setIsSubmittingRewardCode(true);
    setRewardCodeError(null);
    setRewardCodeSuccess(null);
    void redeemRpgProfileRewardCode(rewardCodeInput)
      .then((response: RedeemProfileRewardCodeResponse) => {
        setRewardCodeInput('');
        setRewardCodeSuccess(`已到账 ${response.amountGranted} 陶泥币`);
        void onRechargeSuccess?.();
      })
      .catch((error: unknown) => {
        setRewardCodeError(error instanceof Error ? error.message : '兑换失败');
      })
      .finally(() => setIsSubmittingRewardCode(false));
  };
  const submitDesktopSearch = () => {
    const keyword = desktopSearchKeyword.trim();
    if (!keyword || !onSearchPublicCode || isSearchingPublicCode) {
      return;
    }

    void onSearchPublicCode(keyword);
  };
  const submitMobileSearch = () => {
    const keyword = mobileSearchKeyword.trim();
    if (!keyword || !onSearchPublicCode || isSearchingPublicCode) {
      return;
    }

    void onSearchPublicCode(keyword);
  };
  const desktopHeroEntry =
    featuredShelf[0] ?? latestEntries[0] ?? myEntries[0] ?? null;
  const desktopHeroCover = desktopHeroEntry
    ? resolvePlatformWorldCoverImage(desktopHeroEntry)
    : null;
  const desktopHeroStripEntries = (
    featuredShelf.length > 0 ? featuredShelf : latestEntries
  ).slice(0, 5);
  const desktopTrendingEntries = latestEntries.slice(0, 3);
  const desktopFeaturedGrid = featuredShelf.slice(0, 4);
  const desktopReleaseGrid = latestEntries.slice(0, 6);
  const desktopLibraryPreview = myEntries.slice(0, 2);
  const mobileFeedEntries = useMemo(() => {
    const entryMap = new Map<string, PlatformPublicGalleryCard>();
    const sourceEntries =
      mobileHomeChannel === 'recommend'
        ? [...featuredShelf, ...latestEntries]
        : filterTodayPublishedEntries(latestEntries);

    sourceEntries.forEach((entry) => {
      entryMap.set(buildPublicGalleryCardKey(entry), entry);
    });

    return Array.from(entryMap.values());
  }, [featuredShelf, latestEntries, mobileHomeChannel]);
  const activeRankingConfig = PLATFORM_RANKING_TABS.find(
    (tab) => tab.id === activeRankingTab,
  ) as (typeof PLATFORM_RANKING_TABS)[number];
  const rankingEntries = useMemo(
    () =>
      buildPlatformRankingEntries(publicEntries, activeRankingTab).slice(0, 30),
    [activeRankingTab, publicEntries],
  );
  const categoryPageClass = isDesktopLayout
    ? DESKTOP_PAGE_STAGE_CLASS
    : MOBILE_PAGE_STAGE_CLASS;
  const leadPublicEntry = featuredShelf[0] ?? latestEntries[0] ?? null;
  const openLeadPublicEntry = () => {
    if (leadPublicEntry) {
      onOpenGalleryDetail(leadPublicEntry);
      return;
    }

    onTabChange('category');
  };

  const mobileHomeContent: ReactNode = (
    <div className={`${MOBILE_PAGE_STAGE_CLASS} platform-mobile-home-stage`}>
      <PublicCodeSearchBar
        value={mobileSearchKeyword}
        onChange={setMobileSearchKeyword}
        onSubmit={submitMobileSearch}
        isSearching={isSearchingPublicCode}
      />

      <div className="platform-mobile-home-channelbar flex min-w-0 gap-4 overflow-x-auto pb-1 scrollbar-hide">
        {MOBILE_HOME_CHANNELS.map((channel) => {
          const active = mobileHomeChannel === channel.id;
          return (
            <button
              key={channel.id}
              type="button"
              onClick={() => setMobileHomeChannel(channel.id)}
              className={`platform-mobile-home-channel shrink-0 ${active ? 'platform-mobile-home-channel--active' : ''}`}
            >
              {channel.label}
            </button>
          );
        })}
      </div>

      {platformError ? (
        <div className="rounded-2xl border border-rose-400/20 bg-rose-500/10 px-4 py-3 text-sm leading-6 text-rose-700">
          {platformError}
        </div>
      ) : null}

      {mobileHomeChannel === 'category' ? (
        <section className="platform-category-list-panel">
          {isLoadingPlatform ? (
            <EmptyShelf text="正在读取公开作品..." />
          ) : categoryGroups.length > 0 && activeCategoryGroup ? (
            <>
              <div className="platform-category-filter-row">
                <button
                  type="button"
                  className="platform-category-filter-button"
                >
                  <SlidersHorizontal className="h-4 w-4" />
                  <span>筛选</span>
                  <span className="platform-category-filter-button__count">
                    {activeCategoryGroup.entries.length}
                  </span>
                </button>
                <span className="platform-category-filter-divider" />
                <div className="platform-category-chip-scroll scrollbar-hide">
                  {categoryGroups.map((group) => {
                    const active = group.tag === activeCategoryGroup.tag;

                    return (
                      <button
                        key={group.tag}
                        type="button"
                        onClick={() => setSelectedCategoryTag(group.tag)}
                        className={`platform-category-chip ${active ? 'platform-category-chip--active' : ''}`}
                      >
                        {group.tag}
                      </button>
                    );
                  })}
                </div>
              </div>

              <button type="button" className="platform-category-sort-button">
                <span>按综合排序</span>
                <ChevronDown className="h-3.5 w-3.5" />
              </button>

              <div className="platform-category-game-list">
                {activeCategoryEntries.map((entry) => (
                  <PlatformCategoryGameItem
                    key={`${buildPublicGalleryCardKey(entry)}:mobile-category:${activeCategoryGroup.tag}`}
                    entry={entry}
                    categoryTag={activeCategoryGroup.tag}
                    onClick={() => onOpenGalleryDetail(entry)}
                  />
                ))}
              </div>
            </>
          ) : (
            <EmptyShelf text="公开广场暂时还没有可分类的作品。" />
          )}
        </section>
      ) : (
        <section className="platform-mobile-home-feed">
          {isLoadingPlatform ? (
            <EmptyShelf text="正在读取公开作品..." />
          ) : mobileFeedEntries.length > 0 ? (
            <div className="grid min-w-0 gap-3">
              {mobileFeedEntries.map((entry: PlatformPublicGalleryCard) => (
                <WorldCard
                  key={`${buildPublicGalleryCardKey(entry)}:mobile-feed:${mobileHomeChannel}`}
                  entry={entry}
                  badge={
                    mobileHomeChannel === 'recommend'
                      ? '推荐'
                      : describePublicGalleryCardKind(entry)
                  }
                  metaLabel={entry.authorDisplayName}
                  onClick={() => onOpenGalleryDetail(entry)}
                  className="w-full"
                />
              ))}
            </div>
          ) : (
            <EmptyShelf text="公开广场暂时还没有可展示的作品。" />
          )}
        </section>
      )}
    </div>
  );

  const categoryContent: ReactNode = (
    <div className={categoryPageClass}>
      <section
        className={`${PANEL_SURFACE_CLASS} platform-ranking-panel px-4 py-3.5 sm:px-5 sm:py-4`}
      >
        <div
          className="platform-ranking-tabs flex min-w-0 gap-2 overflow-x-auto pb-1 scrollbar-hide"
          role="tablist"
          aria-label="作品排行"
        >
          {PLATFORM_RANKING_TABS.map((tab) => {
            const active = tab.id === activeRankingTab;

            return (
              <button
                key={tab.id}
                type="button"
                role="tab"
                aria-selected={active}
                onClick={() => setActiveRankingTab(tab.id)}
                className={`platform-ranking-tab shrink-0 ${active ? 'platform-ranking-tab--active' : ''}`}
              >
                {tab.label}
              </button>
            );
          })}
        </div>

        {isLoadingPlatform ? (
          <EmptyShelf text="正在读取公开作品..." />
        ) : rankingEntries.length > 0 ? (
          <div className="mt-3 grid min-w-0 gap-2.5">
            {rankingEntries.map((entry, index) => (
              <PlatformRankingItem
                key={`${buildPublicGalleryCardKey(entry)}:ranking:${activeRankingTab}`}
                entry={entry}
                rank={index + 1}
                metricLabel={activeRankingConfig.metricLabel}
                metricValue={getPlatformRankingMetricValue(
                  entry,
                  activeRankingTab,
                )}
                onClick={() => onOpenGalleryDetail(entry)}
              />
            ))}
          </div>
        ) : (
          <EmptyShelf text={activeRankingConfig.emptyText} />
        )}
      </section>
    </div>
  );

  const createContent: ReactNode = createTabContent ?? (
    <div className={MOBILE_PAGE_STAGE_CLASS}>
      <button
        type="button"
        onClick={onOpenCreateTypePicker}
        className={`${HERO_SURFACE_CLASS} relative block w-full overflow-hidden px-4 py-4 text-left`}
      >
        <div className="absolute inset-0 bg-[var(--platform-hero-overlay-strong)]" />
        <div className="relative z-10 flex min-h-[10rem] flex-col justify-between">
          <span className="platform-pill platform-pill--cool w-fit">
            CREATE
          </span>
          <div className="min-w-0">
            <div className="break-all text-[clamp(1.6rem,7.4vw,1.92rem)] font-black leading-tight text-white">
              开启新的创作
            </div>
            <div className="mt-2 max-w-[28rem] break-all text-sm leading-6 text-zinc-200/88">
              先选择游戏类型，再进入对应的创作工作台继续推进。
            </div>
            <div className="mt-4 flex items-center gap-2 text-sm font-semibold text-white/90">
              <span>选择类型并继续</span>
              <ArrowRight className="h-4 w-4" />
            </div>
          </div>
        </div>
      </button>

      <section>
        <SectionHeader title="我的创作" detail="草稿与已发布" />
        {isLoadingPlatform ? (
          <EmptyShelf text="正在读取你的作品..." />
        ) : myEntries.length > 0 ? (
          <div className="grid grid-cols-2 gap-2.5 sm:gap-3 xl:grid-cols-3">
            {myEntries.map(
              (entry: CustomWorldLibraryEntry<CustomWorldProfile>) => (
                <CreationLibraryCard
                  key={`${entry.ownerUserId}:${entry.profileId}:mine`}
                  entry={entry}
                  onClick={() => onOpenLibraryDetail(entry)}
                  onDelete={
                    onDeleteLibraryEntry
                      ? () => onDeleteLibraryEntry(entry)
                      : undefined
                  }
                  isDeleting={deletingLibraryEntryId === entry.profileId}
                />
              ),
            )}
          </div>
        ) : (
          <EmptyShelf
            text={
              isAuthenticated
                ? '你还没有保存任何自定义世界，先创建一个草稿开始吧。'
                : '登录后查看你的作品。'
            }
          />
        )}
      </section>
    </div>
  );

  const savesContent: ReactNode = (
    <div className={MOBILE_PAGE_STAGE_CLASS}>
      {authUi?.user ? (
        <>
          {saveError ? (
            <div className="rounded-2xl border border-rose-400/20 bg-rose-500/10 px-4 py-3 text-sm leading-6 text-rose-700">
              {saveError}
            </div>
          ) : null}

          <section className={`${PANEL_SURFACE_CLASS} px-4 py-3.5`}>
            <SectionHeader title="全部存档" detail="最近更新时间排序" />
            {isLoadingPlatform ? (
              <EmptyShelf text="正在读取存档..." />
            ) : saveEntries.length > 0 ? (
              <div className="grid gap-3 md:grid-cols-2">
                {saveEntries.map((entry) => (
                  <SaveArchiveCard
                    key={entry.worldKey}
                    entry={entry}
                    loading={isResumingSaveWorldKey === entry.worldKey}
                    onClick={() => onResumeSave(entry)}
                  />
                ))}
              </div>
            ) : (
              <EmptyShelf text="还没有可恢复的存档，去首页开始一段新的游玩吧。" />
            )}
          </section>
        </>
      ) : (
        <section className={`${PANEL_SURFACE_CLASS} px-4 py-3.5`}>
          <div className="platform-subpanel rounded-[1.35rem] px-4 py-4">
            <div className="text-base font-semibold text-[var(--platform-text-strong)]">
              尚未登录
            </div>
            <button
              type="button"
              onClick={() => authUi?.openLoginModal()}
              className="platform-button platform-button--primary mt-4"
            >
              登录
            </button>
          </div>
        </section>
      )}
    </div>
  );

  const profileContent: ReactNode = (
    <div className={MOBILE_PAGE_STAGE_CLASS}>
      {authUi?.user ? (
        <>
          <section className="platform-profile-hero rounded-[1.8rem] p-4">
            <div className="flex items-start justify-between gap-3">
              <div className="flex min-w-0 items-center gap-3">
                <button
                  type="button"
                  onClick={openAvatarPicker}
                  className="platform-profile-avatar relative h-16 w-16 shrink-0 rounded-[1.4rem]"
                  aria-label="上传头像"
                >
                  {avatarUrl ? (
                    <img
                      src={avatarUrl}
                      alt=""
                      className="h-full w-full rounded-[1.4rem] object-cover"
                    />
                  ) : (
                    <span className="flex h-full w-full items-center justify-center text-2xl font-black">
                      {avatarLabel}
                    </span>
                  )}
                  <span className="platform-profile-camera absolute -bottom-1 -right-1 flex h-6 w-6 items-center justify-center rounded-full">
                    <Camera className="h-3.5 w-3.5" />
                  </span>
                </button>
                <input
                  ref={avatarFileInputRef}
                  type="file"
                  accept="image/jpeg,image/png,image/webp"
                  className="hidden"
                  onChange={(event) =>
                    handleAvatarFileChange(event.target.files?.[0] ?? null)
                  }
                />

                <div className="min-w-0">
                  <div className="flex items-center gap-2">
                    <div className="truncate text-xl font-black text-[var(--platform-text-strong)]">
                      {authUi.user.displayName}
                    </div>
                    <button
                      type="button"
                      onClick={openNicknameModal}
                      className="platform-profile-icon-button flex h-7 w-7 items-center justify-center rounded-full"
                      aria-label="修改昵称"
                    >
                      <Pencil className="h-3.5 w-3.5" />
                    </button>
                  </div>
                  <div className="mt-1 flex flex-wrap items-center gap-2 text-xs text-[var(--platform-text-soft)]">
                    <span>陶泥号 {publicUserCode}</span>
                    <button
                      type="button"
                      onClick={copyProfilePublicUserCode}
                      className="platform-profile-chip flex items-center gap-1 rounded-full px-2 py-1"
                    >
                      <Copy className="h-3 w-3" />
                      {profileCopyState === 'copied'
                        ? '已复制'
                        : profileCopyState === 'failed'
                          ? '复制失败'
                          : '复制'}
                    </button>
                  </div>
                </div>
              </div>

              <button
                type="button"
                onClick={openRewardCodeModal}
                className="platform-profile-action flex shrink-0 items-center gap-2 rounded-[1.1rem] px-3 py-2 text-left"
              >
                <Ticket className="h-4 w-4" />
                <div>
                  <div className="text-xs font-bold">兑换码</div>
                  <div className="text-[10px] opacity-80">陶泥币</div>
                </div>
                <ChevronRight className="h-4 w-4 opacity-80" />
              </button>
            </div>
          </section>

          <section className={`${PANEL_SURFACE_CLASS} px-4 py-3.5`}>
            <div className="grid grid-cols-3 gap-3">
              {isLoadingDashboard ? (
                <>
                  <ProfileStatCardSkeleton />
                  <ProfileStatCardSkeleton />
                  <ProfileStatCardSkeleton />
                </>
              ) : dashboardError ? (
                <>
                  <ProfileStatCard
                    cardKey="wallet"
                    label="剩余陶泥币"
                    value="暂不可用"
                    icon={Coins}
                    onClick={openWalletLedgerPanel}
                  />
                  <ProfileStatCard
                    cardKey="playTime"
                    label="总游戏时长"
                    value="暂不可用"
                    icon={Clock3}
                    onClick={onOpenProfileDashboardCard}
                  />
                  <ProfileStatCard
                    cardKey="playedWorks"
                    label="玩过作品"
                    value="暂不可用"
                    icon={BookOpen}
                    onClick={onOpenProfileDashboardCard}
                  />
                </>
              ) : (
                <>
                  <ProfileStatCard
                    cardKey="wallet"
                    label="剩余陶泥币"
                    value={formatDashboardCount(remainingNarrativeCoins)}
                    icon={Coins}
                    onClick={openWalletLedgerPanel}
                  />
                  <ProfileStatCard
                    cardKey="playTime"
                    label="总游戏时长"
                    value={totalPlayTime}
                    icon={Clock3}
                    onClick={onOpenProfileDashboardCard}
                  />
                  <ProfileStatCard
                    cardKey="playedWorks"
                    label="玩过作品"
                    value={formatDashboardCount(playedWorkCount)}
                    icon={BookOpen}
                    onClick={onOpenProfileDashboardCard}
                  />
                </>
              )}
            </div>
            <div className="mt-3 text-[11px] text-[var(--platform-text-soft)]">
              {dashboardError
                ? dashboardError
                : `更新于 ${formatDashboardUpdatedAt(profileDashboard?.updatedAt)}`}
            </div>
          </section>

          <section className={`${PANEL_SURFACE_CLASS} px-4 py-3.5`}>
            <SectionHeader title="常用功能" detail="快捷入口" />
            <div className="grid grid-cols-3 gap-3">
              <ProfileShortcutButton
                label="邀请好友"
                icon={UserPlus}
                onClick={() => openProfilePopupPanel('invite')}
              />
              <ProfileShortcutButton
                label="填邀请码"
                icon={Ticket}
                onClick={() => openProfilePopupPanel('redeem')}
              />
              <ProfileShortcutButton
                label="玩家社区"
                icon={MessageCircle}
                onClick={() => openProfilePopupPanel('community')}
              />
            </div>
          </section>

          <section className={`${PANEL_SURFACE_CLASS} px-4 py-3.5`}>
            <button
              type="button"
              onClick={() => authUi.openSettingsModal()}
              className="platform-subpanel platform-interactive-card flex w-full items-center justify-between gap-3 rounded-[1.25rem] px-4 py-4 text-left transition hover:border-[var(--platform-surface-hover-border)] hover:bg-[var(--platform-button-secondary-fill)]"
            >
              <div className="flex items-center gap-3">
                <div className="platform-profile-chip flex h-10 w-10 items-center justify-center rounded-full">
                  <Settings className="h-[1.125rem] w-[1.125rem]" />
                </div>
                <div>
                  <div className="text-base font-semibold text-[var(--platform-text-strong)]">
                    设置
                  </div>
                  <div className="text-xs text-[var(--platform-text-soft)]">
                    主题与账号
                  </div>
                </div>
              </div>
              <ChevronRight className="h-4 w-4 text-[var(--platform-text-soft)]" />
            </button>
          </section>
        </>
      ) : (
        <section className={`${PANEL_SURFACE_CLASS} px-4 py-3.5`}>
          <div className="platform-subpanel rounded-[1.35rem] px-4 py-4">
            <div className="text-base font-semibold text-[var(--platform-text-strong)]">
              尚未登录
            </div>
            <button
              type="button"
              onClick={() => authUi?.openLoginModal()}
              className="platform-button platform-button--primary mt-4"
            >
              登录
            </button>
          </div>
        </section>
      )}
    </div>
  );

  const desktopHomeContent: ReactNode = (
    <div className={DESKTOP_PAGE_STAGE_CLASS}>
      {platformError ? (
        <div className="rounded-[1.5rem] border border-rose-400/20 bg-rose-500/10 px-4 py-3 text-sm leading-6 text-rose-700">
          {platformError}
        </div>
      ) : null}

      <div className="grid gap-5 xl:grid-cols-[minmax(0,1.55fr)_22rem]">
        <button
          type="button"
          onClick={openLeadPublicEntry}
          className={`${HERO_SURFACE_CLASS} relative block overflow-hidden px-7 py-6 text-left`}
        >
          {desktopHeroCover ? (
            <ResolvedAssetBackdrop
              src={desktopHeroCover}
              alt=""
              aria-hidden="true"
              className="absolute inset-0 h-full w-full object-cover opacity-34"
            />
          ) : null}
          <div className="absolute inset-0 bg-[var(--platform-hero-overlay-strong)]" />
          <div className="relative z-10 flex min-h-[24rem] flex-col justify-between">
            <div className="flex items-start justify-between gap-4">
              <span className="platform-pill platform-pill--warm">作品</span>
              <span className="platform-pill platform-pill--neutral px-3">
                {leadPublicEntry
                  ? describePublicGalleryCardKind(leadPublicEntry)
                  : '作品广场'}
              </span>
            </div>

            <div className="max-w-[35rem]">
              <div className="text-5xl font-semibold leading-[1.08] text-white">
                {leadPublicEntry?.worldName ?? '浏览玩家作品'}
              </div>
              <div className="mt-4 text-base leading-8 text-zinc-200/86">
                {leadPublicEntry?.summaryText ||
                  leadPublicEntry?.subtitle ||
                  '从公开广场进入作品详情，挑一个世界开始游玩。'}
              </div>
              <div className="mt-5 inline-flex items-center gap-2 rounded-full border border-white/18 bg-white/18 px-4 py-2 text-sm font-semibold text-white/92">
                <span>查看作品</span>
                <ArrowRight className="h-4 w-4" />
              </div>
            </div>

            {desktopHeroStripEntries.length > 0 ? (
              <div className="grid gap-3 sm:grid-cols-5">
                {desktopHeroStripEntries.map((entry, index) => {
                  const coverImage = resolvePlatformWorldCoverImage(entry);
                  const displayName = formatPlatformWorkDisplayName(
                    entry.worldName,
                  );
                  return (
                    <div
                      key={`${entry.ownerUserId}:${entry.profileId}:hero-strip`}
                      className="platform-subpanel overflow-hidden rounded-[1.15rem]"
                    >
                      <div className="relative aspect-[1.35/1] overflow-hidden">
                        {coverImage ? (
                          <ResolvedAssetBackdrop
                            src={coverImage}
                            alt=""
                            aria-hidden="true"
                            className="h-full w-full object-cover"
                          />
                        ) : null}
                        <div className="absolute inset-0 bg-[linear-gradient(180deg,rgba(255,255,255,0.04),rgba(91,24,46,0.34))]" />
                      </div>
                      <div className="flex items-center gap-2 px-3 py-2 text-[11px] text-[color:color-mix(in_srgb,var(--platform-text-base)_82%,transparent)]">
                        <span className="text-[var(--platform-text-soft)]">
                          {`${index + 1}`.padStart(2, '0')}
                        </span>
                        <span className="line-clamp-1">{displayName}</span>
                      </div>
                    </div>
                  );
                })}
              </div>
            ) : null}
          </div>
        </button>

        <section className="platform-desktop-panel px-5 py-5">
          <div className="mb-4 flex items-start justify-between gap-3">
            <SectionHeader title="趋势关注" detail="TRENDING NOW" />
            <span className="platform-pill platform-pill--neutral px-3">
              LIVE
            </span>
          </div>
          {isLoadingPlatform ? (
            <EmptyShelf text="正在整理趋势作品..." />
          ) : desktopTrendingEntries.length > 0 ? (
            <div className="space-y-3">
              {desktopTrendingEntries.map((entry, index) => (
                <DesktopTrendingItem
                  key={`${buildPublicGalleryCardKey(entry)}:desktop-trend`}
                  entry={entry}
                  rank={index + 1}
                  onClick={() => onOpenGalleryDetail(entry)}
                />
              ))}
            </div>
          ) : (
            <EmptyShelf text="公开广场暂时还没有趋势作品。" />
          )}
        </section>
      </div>

      <div className="grid gap-5 2xl:grid-cols-[minmax(0,1.2fr)_minmax(22rem,0.8fr)]">
        <section className="platform-desktop-panel px-5 py-5">
          <SectionHeader title="精选推荐" detail="CURATED WORLDS" />
          {isLoadingPlatform ? (
            <EmptyShelf text="正在读取精选作品..." />
          ) : desktopFeaturedGrid.length > 0 ? (
            <div className="grid gap-4 xl:grid-cols-2">
              {desktopFeaturedGrid.map((entry) => (
                <WorldCard
                  key={`${buildPublicGalleryCardKey(entry)}:desktop-featured`}
                  entry={entry}
                  badge="推荐"
                  metaLabel={describePublicGalleryCardKind(entry)}
                  onClick={() => onOpenGalleryDetail(entry)}
                  className="w-full min-w-0"
                />
              ))}
            </div>
          ) : (
            <EmptyShelf text="公开广场暂时还没有精选作品。" />
          )}
        </section>

        <section className="platform-desktop-panel px-5 py-5">
          <SectionHeader
            title={
              desktopLibraryPreview.length > 0
                ? '最近作品'
                : historyEntries.length > 0
                  ? '最近浏览'
                  : '作品广场'
            }
            detail="QUICK ACCESS"
          />

          <div>
            <div className="text-[10px] font-semibold tracking-[0.24em] text-[var(--platform-text-soft)]">
              {desktopLibraryPreview.length > 0
                ? '最近作品'
                : historyEntries.length > 0
                  ? '最近浏览'
                  : '公开作品'}
            </div>

            {desktopLibraryPreview.length > 0 ? (
              <div className="mt-3 space-y-3">
                {desktopLibraryPreview.map((entry) => {
                  const displayName = formatPlatformWorkDisplayName(
                    entry.worldName,
                  );

                  return (
                    <button
                      key={`${entry.ownerUserId}:${entry.profileId}:desktop-mine`}
                      type="button"
                      onClick={() => onOpenLibraryDetail(entry)}
                      className="platform-desktop-trending-item flex w-full items-center justify-between gap-3 px-4 py-4 text-left"
                    >
                      <div className="min-w-0">
                        <div className="line-clamp-1 text-base font-semibold text-[var(--platform-text-strong)]">
                          {displayName}
                        </div>
                        <div className="mt-1 text-sm text-[var(--platform-text-soft)]">
                          {entry.visibility === 'published'
                            ? `发布于 ${formatPlatformWorldTime(entry.publishedAt)}`
                            : '草稿待完善'}
                        </div>
                      </div>
                      <span className="platform-pill platform-pill--neutral px-3">
                        {entry.visibility === 'published' ? '已发布' : '草稿'}
                      </span>
                    </button>
                  );
                })}
              </div>
            ) : historyEntries.length > 0 ? (
              <div className="mt-3 space-y-3">
                {historyEntries.slice(0, 2).map((entry) => {
                  const displayName = formatPlatformWorkDisplayName(
                    entry.worldName,
                  );

                  return (
                    <button
                      key={`${entry.ownerUserId}:${entry.profileId}:desktop-history`}
                      type="button"
                      onClick={() =>
                        onOpenGalleryDetail({
                          ownerUserId: entry.ownerUserId,
                          profileId: entry.profileId,
                          publicWorkCode: null,
                          authorPublicUserCode: null,
                          visibility: 'published',
                          publishedAt: entry.visitedAt,
                          updatedAt: entry.visitedAt,
                          worldName: entry.worldName,
                          subtitle: entry.subtitle,
                          summaryText: entry.summaryText,
                          coverImageSrc: entry.coverImageSrc,
                          themeMode: entry.themeMode,
                          authorDisplayName: entry.authorDisplayName,
                          playableNpcCount: 0,
                          landmarkCount: 0,
                          likeCount: 0,
                        })
                      }
                      className="platform-desktop-trending-item flex w-full items-center justify-between gap-3 px-4 py-4 text-left"
                    >
                      <div className="min-w-0">
                        <div className="line-clamp-1 text-base font-semibold text-[var(--platform-text-strong)]">
                          {displayName}
                        </div>
                        <div className="mt-1 text-sm text-[var(--platform-text-soft)]">
                          作者：{entry.authorDisplayName}
                        </div>
                      </div>
                      <span className="platform-pill platform-pill--neutral px-3">
                        浏览
                      </span>
                    </button>
                  );
                })}
              </div>
            ) : (
              <div className="platform-subpanel mt-3 rounded-[1.35rem] px-4 py-4 text-sm leading-6 text-[var(--platform-text-base)]">
                公开广场暂时还没有可展示的作品。
              </div>
            )}
          </div>
        </section>
      </div>

      <section className="platform-desktop-panel px-5 py-5">
        <SectionHeader title="最新发布" detail="PLAYER SQUARE" />
        {isLoadingPlatform ? (
          <EmptyShelf text="正在读取最新发布..." />
        ) : desktopReleaseGrid.length > 0 ? (
          <div className="grid gap-4 xl:grid-cols-3">
            {desktopReleaseGrid.map((entry) => (
              <WorldCard
                key={`${buildPublicGalleryCardKey(entry)}:desktop-latest`}
                entry={entry}
                badge={describePublicGalleryCardKind(entry)}
                metaLabel={entry.authorDisplayName}
                onClick={() => onOpenGalleryDetail(entry)}
                className="w-full min-w-0"
              />
            ))}
          </div>
        ) : (
          <EmptyShelf text="公开广场暂时还没有新作品。" />
        )}
      </section>
    </div>
  );

  const tabContentById = {
    home: isDesktopLayout ? desktopHomeContent : mobileHomeContent,
    category: categoryContent,
    create: createContent,
    saves: savesContent,
    profile: profileContent,
  } satisfies Record<PlatformHomeTab, ReactNode>;
  const tabPanels = PLATFORM_HOME_TABS.filter((tab) =>
    visibleTabs.includes(tab),
  ).map((tab) => {
    const shouldMountPanel = tab === activeTab || visitedTabs.has(tab);

    return (
      <PlatformTabPanel key={tab} tab={tab} activeTab={activeTab}>
        {shouldMountPanel ? tabContentById[tab] : null}
      </PlatformTabPanel>
    );
  });
  const profileEditModals: ReactNode = (
    <>
      {isNicknameModalOpen ? (
        <ProfileNicknameModal
          value={nicknameInput}
          error={nicknameError}
          isSaving={isSavingNickname}
          onChange={(value) => {
            setNicknameInput(value);
            setNicknameError(null);
          }}
          onClose={() => setIsNicknameModalOpen(false)}
          onSubmit={submitNickname}
        />
      ) : null}
      {avatarSource && avatarImageSize ? (
        <ProfileAvatarCropModal
          source={avatarSource}
          imageSize={avatarImageSize}
          scale={avatarScale}
          cropX={avatarCrop.x}
          cropY={avatarCrop.y}
          error={avatarError}
          isSaving={isSavingAvatar}
          onScaleChange={updateAvatarScale}
          onCropChange={updateAvatarCrop}
          onClose={() => {
            setAvatarSource(null);
            setAvatarImageSize(null);
            setAvatarError(null);
          }}
          onSubmit={submitAvatar}
        />
      ) : null}
      {avatarError && !avatarSource ? (
        <div className="pointer-events-none fixed left-1/2 top-5 z-[90] w-[min(92vw,22rem)] -translate-x-1/2 rounded-2xl border border-rose-400/25 bg-white px-4 py-3 text-center text-sm font-semibold text-rose-600 shadow-2xl">
          {avatarError}
        </div>
      ) : null}
    </>
  );
  const rewardCodeModal: ReactNode = isRewardCodeOpen ? (
    <RewardCodeRedeemModal
      value={rewardCodeInput}
      isSubmitting={isSubmittingRewardCode}
      error={rewardCodeError}
      success={rewardCodeSuccess}
      onChange={setRewardCodeInput}
      onSubmit={submitRewardCode}
      onClose={() => setIsRewardCodeOpen(false)}
    />
  ) : null;

  if (!isDesktopLayout) {
    return (
      <div className="platform-mobile-entry-shell flex h-full min-h-0 min-w-0 flex-col overflow-hidden">
        <div className="mb-3 flex shrink-0 items-center justify-between gap-3 px-0.5">
          <RpgEntryBrandLogo />
          {!isAuthenticated ? (
            <button
              type="button"
              onClick={openUserSurface}
              className="platform-button platform-button--primary shrink-0 px-3 py-2 text-xs"
            >
              <LogIn className="h-3.5 w-3.5" />
              登录
            </button>
          ) : null}
        </div>

        <div className="platform-tab-panel-stack min-w-0 flex-1">
          {tabPanels}
        </div>

        <div
          className="platform-mobile-bottom-dock mt-3 min-w-0 shrink-0 border-t pt-2"
          style={{
            borderColor: 'var(--platform-line-soft)',
          }}
        >
          <div
            className={`platform-bottom-nav grid ${visibleTabs.length === 5 ? 'grid-cols-5' : visibleTabs.length === 4 ? 'grid-cols-4' : visibleTabs.length === 3 ? 'grid-cols-3' : 'grid-cols-2'}`}
          >
            {visibleTabs.map((tab) => (
              <PlatformTabButton
                key={tab}
                active={activeTab === tab}
                label={tabLabels[tab]}
                icon={tabIcons[tab]}
                emphasized={tab === 'create'}
                onClick={() => onTabChange(tab)}
              />
            ))}
          </div>
        </div>
        {profilePopupPanel ? (
          <ProfileReferralModal
            panel={profilePopupPanel}
            center={referralCenter}
            inviteCodeInput={inviteCodeInput}
            isLoading={isLoadingReferral}
            isSubmitting={isSubmittingReferral}
            error={referralError}
            success={referralSuccess}
            onClose={() => setProfilePopupPanel(null)}
            onInputChange={setInviteCodeInput}
            onCopyInvite={copyInviteInfo}
            onSubmitRedeem={submitReferralInviteCode}
          />
        ) : null}
        {rewardCodeModal}
        {isProfilePlayStatsOpen ? (
          <ProfilePlayedWorksModal
            stats={profilePlayStats}
            isLoading={isProfilePlayStatsLoading}
            error={profilePlayStatsError}
            onClose={onCloseProfilePlayStats ?? (() => undefined)}
            onOpenWork={onOpenPlayedWork}
          />
        ) : null}
        {isWalletLedgerOpen ? (
          <WalletLedgerModal
            ledger={walletLedger}
            fallbackBalance={remainingNarrativeCoins}
            isLoading={isLoadingWalletLedger}
            error={walletLedgerError}
            onClose={() => setIsWalletLedgerOpen(false)}
            onRetry={loadWalletLedger}
          />
        ) : null}
        {profileEditModals}
      </div>
    );
  }

  return (
    <div className="flex h-full min-h-0 flex-col">
      <div className="flex h-full min-h-0 flex-col">
        <div className="platform-desktop-shell flex h-full min-h-0 flex-col p-5 xl:p-6">
          <div className="platform-desktop-topbar flex items-center gap-4 px-5 py-4">
            <div className="flex min-w-0 flex-1 items-center gap-5">
              <RpgEntryBrandLogo className="shrink-0" decorative />
              <PublicCodeSearchBar
                value={desktopSearchKeyword}
                onChange={setDesktopSearchKeyword}
                onSubmit={submitDesktopSearch}
                isSearching={
                  !onSearchPublicCode || Boolean(isSearchingPublicCode)
                }
                className="max-w-[34rem] flex-1"
              />
            </div>

            <div className="flex items-center gap-3">
              <button
                type="button"
                onClick={openUserSurface}
                className="platform-icon-button"
                aria-label="通知与账户"
              >
                <Bell className="h-4 w-4" />
              </button>
              <button
                type="button"
                onClick={openUserSurface}
                className="platform-desktop-search flex items-center gap-3 px-3 py-2.5 text-left"
              >
                <span
                  className="flex h-11 w-11 items-center justify-center overflow-hidden rounded-full text-base font-black text-white"
                  style={{
                    background: 'var(--platform-profile-avatar-fill)',
                    boxShadow: 'var(--platform-profile-avatar-shadow)',
                  }}
                >
                  {avatarUrl ? (
                    <img
                      src={avatarUrl}
                      alt=""
                      className="h-full w-full object-cover"
                    />
                  ) : (
                    avatarLabel
                  )}
                </span>
                <span className="min-w-0">
                  <span className="block truncate text-sm font-semibold text-[var(--platform-text-strong)]">
                    {authUi?.user?.displayName || '登录'}
                  </span>
                  <span className="block truncate text-xs text-[var(--platform-text-soft)]">
                    {authUi?.user ? publicUserCode : '账号入口'}
                  </span>
                </span>
              </button>
            </div>
          </div>

          <div className="mt-5 flex min-h-0 gap-5">
            <aside className="platform-desktop-rail flex w-[5.8rem] shrink-0 flex-col gap-3 p-3">
              {visibleTabs.map((tab) => (
                <DesktopTabButton
                  key={tab}
                  active={activeTab === tab}
                  label={tabLabels[tab]}
                  icon={tabIcons[tab]}
                  emphasized={tab === 'create'}
                  onClick={() => onTabChange(tab)}
                />
              ))}
            </aside>

            <div className="platform-tab-panel-stack min-w-0 flex-1">
              {tabPanels}
            </div>
          </div>
        </div>
      </div>
      {rewardCodeModal}
      {profilePopupPanel ? (
        <ProfileReferralModal
          panel={profilePopupPanel}
          center={referralCenter}
          inviteCodeInput={inviteCodeInput}
          isLoading={isLoadingReferral}
          isSubmitting={isSubmittingReferral}
          error={referralError}
          success={referralSuccess}
          onClose={() => setProfilePopupPanel(null)}
          onInputChange={setInviteCodeInput}
          onCopyInvite={copyInviteInfo}
          onSubmitRedeem={submitReferralInviteCode}
        />
      ) : null}
      {isProfilePlayStatsOpen ? (
        <ProfilePlayedWorksModal
          stats={profilePlayStats}
          isLoading={isProfilePlayStatsLoading}
          error={profilePlayStatsError}
          onClose={onCloseProfilePlayStats ?? (() => undefined)}
          onOpenWork={onOpenPlayedWork}
        />
      ) : null}
      {isWalletLedgerOpen ? (
        <WalletLedgerModal
          ledger={walletLedger}
          fallbackBalance={remainingNarrativeCoins}
          isLoading={isLoadingWalletLedger}
          error={walletLedgerError}
          onClose={() => setIsWalletLedgerOpen(false)}
          onRetry={loadWalletLedger}
        />
      ) : null}
      {profileEditModals}
    </div>
  );
}

export const PlatformHomeView = RpgEntryHomeView;
