import {
  type ReactNode,
  useDeferredValue,
  useEffect,
  useMemo,
  useRef,
  useState,
} from 'react';

import { getCustomWorldSceneRelativePositionLabel } from '../data/customWorldSceneGraph';
import { normalizeCustomWorldCreatorIntent } from '../services/customWorldCreatorIntent';
import {
  buildCustomWorldFoundationEntries,
  parseFoundationTagText,
} from '../services/customWorldFoundationEntries';
import { buildCustomWorldScenePresentations } from '../services/customWorldScenePresentation';
import {
  AnimationState,
  type Character,
  type CustomWorldOpeningCgProfile,
  type CustomWorldProfile,
  type SceneActBlueprint,
  type SceneChapterBlueprint,
} from '../types';
import { CharacterAnimator } from './CharacterAnimator';
import { PlatformAcknowledgeStatusDialog } from './common/PlatformAcknowledgeStatusDialog';
import { PlatformActionButton } from './common/PlatformActionButton';
import { PlatformDangerConfirmDialog } from './common/PlatformDangerConfirmDialog';
import { PlatformEmptyState } from './common/PlatformEmptyState';
import { PlatformMediaFrame } from './common/PlatformMediaFrame';
import { PlatformPillBadge } from './common/PlatformPillBadge';
import { PlatformProgressBar } from './common/PlatformProgressBar';
import { PlatformSegmentedTabs } from './common/PlatformSegmentedTabs';
import { PlatformStatGrid } from './common/PlatformStatGrid';
import { PlatformStatusMessage } from './common/PlatformStatusMessage';
import { PlatformSubpanel } from './common/PlatformSubpanel';
import { PlatformTextField } from './common/PlatformTextField';
import { CustomWorldNpcPortrait } from './CustomWorldNpcVisualEditor';
import { ResolvedAssetImage } from './ResolvedAssetImage';
import { ResolvedAssetVideo } from './ResolvedAssetVideo';
import type { RpgCreationEditorTarget } from './rpg-creation-editor/RpgCreationEntityEditorModal';

export type ResultTab = 'world' | 'playable' | 'story' | 'landmarks';

type PendingGeneratedEntity = {
  id: string;
  kind: 'playable' | 'story' | 'landmark';
  title: string;
  progress: number;
  phaseLabel: string;
};

type RecentGeneratedIds = Record<'playable' | 'story' | 'landmark', string[]>;

interface CustomWorldEntityCatalogProps {
  profile: CustomWorldProfile;
  previewCharacters: Character[];
  activeTab: ResultTab;
  onActiveTabChange: (tab: ResultTab) => void;
  onEditTarget: (target: RpgCreationEditorTarget) => void;
  onProfileChange: (profile: CustomWorldProfile) => void;
  onDeleteStoryNpcs?: (ids: string[]) => void;
  onDeleteLandmarks?: (ids: string[]) => void;
  createActionLabel?: string;
  onCreateAction?: () => void;
  createActionDisabled?: boolean;
  openingCgGenerating?: boolean;
  openingCgPhaseLabel?: string | null;
  openingCgGenerateDisabled?: boolean;
  onGenerateOpeningCg?: () => void;
  pendingGeneratedEntity?: PendingGeneratedEntity | null;
  recentGeneratedIds?: RecentGeneratedIds;
  readOnly?: boolean;
}

const RESULT_TABS: Array<{ id: ResultTab; label: string }> = [
  { id: 'world', label: '世界' },
  { id: 'landmarks', label: '场景' },
  { id: 'playable', label: '可扮演角色' },
  { id: 'story', label: '场景角色' },
];

function Section({
  title,
  subtitle,
  badge,
  actions,
  children,
  className = '',
}: {
  title: string;
  subtitle?: string;
  badge?: ReactNode;
  actions?: ReactNode;
  children: ReactNode;
  className?: string;
}) {
  return (
    <div
      className={`platform-surface platform-surface--soft px-3.5 py-3 ${className}`}
    >
      <div className="flex items-start justify-between gap-3">
        <div className="min-w-0">
          <div className="text-xs font-bold tracking-[0.16em] text-white">
            {title}
          </div>
          {subtitle ? (
            <div className="mt-1 text-xs leading-6 text-zinc-500">
              {subtitle}
            </div>
          ) : null}
        </div>
        <div className="flex items-center gap-2">
          {badge}
          {actions}
        </div>
      </div>
      <div className="mt-3">{children}</div>
    </div>
  );
}

function SmallButton({
  onClick,
  children,
  tone = 'default',
  disabled = false,
}: {
  onClick: React.MouseEventHandler<HTMLButtonElement>;
  children: ReactNode;
  tone?: 'default' | 'sky' | 'rose';
  disabled?: boolean;
  actions?: ReactNode;
}) {
  return (
    <PlatformActionButton
      onClick={onClick}
      disabled={disabled}
      tone={tone === 'sky' ? 'primary' : tone === 'rose' ? 'danger' : 'ghost'}
      shape="pill"
      size="xs"
      className="min-h-0 py-1 text-[11px]"
    >
      {children}
    </PlatformActionButton>
  );
}

function SearchBox({
  value,
  onChange,
  placeholder,
}: {
  value: string;
  onChange: (value: string) => void;
  placeholder: string;
}) {
  return (
    <PlatformTextField
      value={value}
      onChange={(event) => onChange(event.target.value)}
      placeholder={placeholder}
      density="compact"
      className="rounded-2xl bg-[var(--platform-subpanel-fill)] px-3 py-2 placeholder:text-[var(--platform-text-soft)]"
    />
  );
}

function EmptyState({ title }: { title: string }) {
  return (
    <PlatformEmptyState
      surface="dashed"
      size="compact"
      className="rounded-2xl px-5 py-6 text-center"
    >
      <div className="text-sm text-[var(--platform-text-base)]">{title}</div>
    </PlatformEmptyState>
  );
}

function buildFallbackRenderKey(
  value: string | null | undefined,
  fallback: string,
) {
  const normalizedValue = value?.trim();
  return normalizedValue ? normalizedValue : fallback;
}

function NewBadge() {
  return (
    <PlatformPillBadge tone="warning" size="xxs" className="font-semibold">
      新
    </PlatformPillBadge>
  );
}

function PendingEntityCard({
  title,
  phaseLabel,
  progress,
}: {
  title: string;
  phaseLabel: string;
  progress: number;
}) {
  return (
    <PlatformStatusMessage
      tone="info"
      surface="platform"
      size="md"
      className="rounded-[1.35rem] py-4"
    >
      <div className="flex items-start justify-between gap-3">
        <div>
          <div className="text-sm font-semibold text-[var(--platform-text-strong)]">
            {title}
          </div>
          <div className="mt-1 text-xs leading-6">{phaseLabel}</div>
        </div>
        <PlatformPillBadge tone="cool" size="xxs">
          {Math.round(progress)}%
        </PlatformPillBadge>
      </div>
      <PlatformProgressBar
        value={progress}
        minVisibleValue={6}
        size="sm"
        ariaLabel={`${title} 进度`}
        className="mt-3"
        fillClassName="bg-[var(--platform-button-primary-solid)]"
      />
    </PlatformStatusMessage>
  );
}

function OpeningCgPreview({
  openingCg,
  isGenerating,
  phaseLabel,
  generateDisabled,
  readOnly,
  onGenerate,
}: {
  openingCg?: CustomWorldOpeningCgProfile | null;
  isGenerating: boolean;
  phaseLabel?: string | null;
  generateDisabled?: boolean;
  readOnly: boolean;
  onGenerate?: () => void;
}) {
  const hasVideo = Boolean(openingCg?.videoSrc?.trim());
  const buttonLabel = hasVideo ? '重新生成' : '生成';

  return (
    <div className="space-y-3">
      <div className="overflow-hidden rounded-2xl border border-[var(--platform-subpanel-border)] bg-black/35 aspect-video">
        {hasVideo ? (
          <ResolvedAssetVideo
            src={openingCg?.videoSrc}
            className="h-full w-full object-cover"
            controls
            playsInline
            preload="metadata"
          />
        ) : openingCg?.storyboardImageSrc ? (
          <ResolvedAssetImage
            src={openingCg.storyboardImageSrc}
            alt="开局 CG 故事板"
            className="h-full w-full object-cover"
          />
        ) : (
          <div className="flex h-full w-full items-center justify-center text-sm font-semibold tracking-[0.18em] text-zinc-500">
            开局 CG
          </div>
        )}
      </div>
      <div className="flex flex-wrap items-center gap-2">
        <PlatformPillBadge tone="neutral" size="xxs">
          80 积分
        </PlatformPillBadge>
        <PlatformPillBadge tone="neutral" size="xxs">
          预计 10 分钟
        </PlatformPillBadge>
        {hasVideo ? (
          <PlatformPillBadge tone="success" size="xxs">
            已生成
          </PlatformPillBadge>
        ) : null}
        {!readOnly && onGenerate ? (
          <div className="ml-auto">
            <SmallButton
              onClick={onGenerate}
              tone="sky"
              disabled={isGenerating || generateDisabled}
            >
              {isGenerating ? (phaseLabel ?? '生成中') : buttonLabel}
            </SmallButton>
          </div>
        ) : null}
      </div>
      {isGenerating ? (
        <PlatformProgressBar
          value={66}
          ariaLabel={`${buttonLabel} 进度`}
          indeterminate
          fillClassName="animate-pulse bg-[linear-gradient(90deg,#df7f40_0%,#cc754c_52%,#eaccb3_100%)]"
        />
      ) : null}
      {openingCg?.status === 'failed' && openingCg.errorMessage ? (
        <PlatformStatusMessage
          tone="error"
          surface="platform"
          size="xs"
          className="rounded-2xl leading-5"
        >
          {openingCg.errorMessage}
        </PlatformStatusMessage>
      ) : null}
    </div>
  );
}

function buildSceneActParticipantText(
  act: SceneActBlueprint,
  roleById: Map<
    string,
    | CustomWorldProfile['playableNpcs'][number]
    | CustomWorldProfile['storyNpcs'][number]
  >,
) {
  const primaryRoleName = roleById.get(act.primaryNpcId)?.name?.trim() || '';
  const supportRoleNames = act.encounterNpcIds
    .filter((roleId) => roleId !== act.primaryNpcId)
    .map((roleId) => roleById.get(roleId)?.name?.trim() || '')
    .filter(Boolean);

  return compactTextList([
    primaryRoleName ? `主角色：${primaryRoleName}` : '',
    supportRoleNames.length > 0
      ? `相遇角色：${supportRoleNames.join('、')}`
      : '',
  ]).join('；');
}

function buildSceneChapterSearchText(
  sceneChapters: SceneChapterBlueprint[],
  roleById: Map<
    string,
    | CustomWorldProfile['playableNpcs'][number]
    | CustomWorldProfile['storyNpcs'][number]
  >,
) {
  return sceneChapters
    .flatMap((chapter) => [
      chapter.title,
      chapter.summary,
      chapter.sceneTaskDescription,
      ...chapter.acts.flatMap((act) => [
        act.title,
        act.summary,
        act.actGoal,
        act.transitionHook,
        buildSceneActParticipantText(act, roleById),
      ]),
    ])
    .filter(Boolean)
    .join(' ');
}

function buildSceneTaskDescriptionText(sceneChapters: SceneChapterBlueprint[]) {
  return (
    compactTextList(
      sceneChapters.map((chapter) => chapter.sceneTaskDescription),
    )[0] ?? ''
  );
}

function SceneActPreviewStrip({
  acts,
  sceneName,
}: {
  acts: Array<{ id: string; title: string; imageSrc: string }>;
  sceneName: string;
}) {
  if (acts.length <= 0) return null;

  return (
    <div className="flex w-full gap-1.5 overflow-x-auto pb-0.5">
      {acts.map((act) => (
        <PlatformSubpanel
          key={act.id}
          as="div"
          padding="none"
          radius="sm"
          className="h-12 w-[5.25rem] shrink-0 overflow-hidden rounded-xl"
          aria-label={`${sceneName}-${act.title}预览`}
        >
          <ResolvedAssetImage
            src={act.imageSrc}
            alt={`${sceneName}-${act.title}`}
            className="h-full w-full object-cover"
          />
        </PlatformSubpanel>
      ))}
    </div>
  );
}

function CatalogCard({
  title,
  description,
  media,
  badge,
  isSelectionMode,
  isSelected,
  onClick,
  layout = 'stacked',
  mediaClassName,
  disabled = false,
  actions,
}: {
  title: string;
  description: string;
  media: ReactNode;
  badge?: ReactNode;
  isSelectionMode: boolean;
  isSelected: boolean;
  onClick: () => void;
  layout?: 'stacked' | 'compact';
  mediaClassName?: string;
  disabled?: boolean;
  actions?: ReactNode;
}) {
  const selectionBadge = isSelectionMode ? (
    <PlatformPillBadge
      tone={isSelected ? 'danger' : 'muted'}
      size="xs"
      className="shrink-0 py-1 text-[10px]"
    >
      {isSelected ? '已选' : '选择'}
    </PlatformPillBadge>
  ) : null;

  if (layout === 'compact') {
    return (
      <PlatformSubpanel
        as="button"
        tabIndex={disabled ? -1 : 0}
        onClick={disabled ? undefined : onClick}
        disabled={disabled}
        aria-disabled={disabled}
        padding="none"
        radius="md"
        surface={isSelected ? 'danger' : 'platform'}
        className="w-full rounded-[1.3rem] p-2.5 text-left transition-colors xl:p-3"
      >
        <div className="flex items-start gap-3 xl:gap-3.5">
          <PlatformSubpanel
            as="div"
            padding="none"
            radius="sm"
            className={`shrink-0 overflow-hidden rounded-[1rem] ${mediaClassName ?? 'h-[4.75rem] w-[4.75rem]'}`}
          >
            {media}
          </PlatformSubpanel>
          <div className="min-w-0 flex-1 xl:min-h-[5.6rem]">
            <div className="flex items-start justify-between gap-3">
              <div className="min-w-0 text-[15px] font-semibold leading-5 text-white xl:line-clamp-1">
                {title}
              </div>
              <div className="flex items-center gap-2">
                {badge}
                {selectionBadge}
              </div>
            </div>
            <div className="mt-1.5 text-sm leading-5 text-zinc-300 xl:line-clamp-2">
              {description || '暂无描述'}
            </div>
            {actions ? (
              <div className="mt-2 flex flex-wrap gap-2">{actions}</div>
            ) : null}
          </div>
        </div>
      </PlatformSubpanel>
    );
  }

  return (
    <PlatformSubpanel
      as="button"
      tabIndex={disabled ? -1 : 0}
      onClick={disabled ? undefined : onClick}
      disabled={disabled}
      aria-disabled={disabled}
      padding="none"
      radius="md"
      surface={isSelected ? 'danger' : 'platform'}
      className="w-full rounded-[1.4rem] p-3 text-left transition-colors"
    >
      <div className="space-y-3">
        <PlatformSubpanel
          as="div"
          padding="none"
          radius="sm"
          className={`overflow-hidden rounded-[1.1rem] ${mediaClassName ?? ''}`}
        >
          {media}
        </PlatformSubpanel>
        <div className="flex items-start justify-between gap-3">
          <div className="min-w-0 text-base font-semibold text-white">
            {title}
          </div>
          <div className="flex items-center gap-2">
            {badge}
            {selectionBadge}
          </div>
        </div>
        <div className="text-sm leading-6 text-zinc-300">
          {description || '暂无描述'}
        </div>
        {actions ? <div className="flex flex-wrap gap-2">{actions}</div> : null}
      </div>
    </PlatformSubpanel>
  );
}

function matchText(text: string, query: string) {
  return text.toLowerCase().includes(query.toLowerCase());
}

function getSearchPlaceholder(tab: ResultTab) {
  if (tab === 'playable') return '搜索角色名称、称号、标签';
  if (tab === 'story') return '搜索场景角色名称、身份、动机';
  if (tab === 'landmarks') return '搜索场景名称、描述、NPC、连接';
  return '搜索';
}

function compactTextList(values: Array<string | null | undefined>) {
  return values.map((value) => value?.trim() ?? '').filter(Boolean);
}

function buildPlayableRoleCardDescription(
  role: CustomWorldProfile['playableNpcs'][number],
) {
  const summary =
    role.description.trim() ||
    role.backstoryReveal.publicSummary.trim() ||
    role.backstory.trim() ||
    role.motivation.trim();

  return compactTextList([role.title || role.role, summary]).join(' / ');
}

function resolvePlayableRolePreviewImage(
  role: CustomWorldProfile['playableNpcs'][number],
  previewCharacter: Character | null,
) {
  if (role.imageSrc?.trim()) {
    return role.imageSrc;
  }

  if (previewCharacter?.portrait?.trim()) {
    return previewCharacter.portrait;
  }

  if (previewCharacter?.avatar?.trim()) {
    return previewCharacter.avatar;
  }

  return '';
}

function buildOpeningSceneSearchText(
  profile: CustomWorldProfile,
  campScene: { name: string; description: string },
) {
  return [
    campScene.name,
    campScene.description,
    profile.playerGoal,
    profile.summary,
    '开局场景',
    '开局归处',
  ].join(' ');
}

type CatalogRole =
  | CustomWorldProfile['playableNpcs'][number]
  | CustomWorldProfile['storyNpcs'][number];

type BulkDeleteTab = 'story' | 'landmarks';

type EntityCatalogConfirmState =
  | { kind: 'minimum-playable' }
  | { kind: 'delete-playable'; id: string; name: string }
  | { kind: 'bulk-delete'; tab: BulkDeleteTab; ids: string[]; label: string };

function buildRoleSearchText(role: CatalogRole) {
  return [
    role.name,
    role.title,
    role.role,
    role.description,
    role.backstory,
    role.backstoryReveal.publicSummary,
    role.personality,
    role.motivation,
    role.combatStyle,
    ...role.backstoryReveal.chapters.flatMap((chapter) => [
      chapter.title,
      chapter.teaser,
      chapter.content,
      chapter.contextSnippet,
    ]),
    ...role.skills.flatMap((skill) => [skill.name, skill.summary, skill.style]),
    ...role.initialItems.flatMap((item) => [
      item.name,
      item.category,
      item.description,
      ...item.tags,
    ]),
    ...role.relationshipHooks,
    ...role.tags,
  ].join(' ');
}

function buildLandmarkSearchText(
  landmark: CustomWorldProfile['landmarks'][number],
  storyNpcById: Map<string, CustomWorldProfile['storyNpcs'][number]>,
  landmarkById: Map<string, CustomWorldProfile['landmarks'][number]>,
) {
  return [
    landmark.name,
    landmark.description,
    ...landmark.sceneNpcIds.map((npcId) => storyNpcById.get(npcId)?.name ?? ''),
    ...landmark.connections.flatMap((connection) => [
      landmarkById.get(connection.targetLandmarkId)?.name ?? '',
      getCustomWorldSceneRelativePositionLabel(connection.relativePosition),
      connection.summary,
    ]),
  ].join(' ');
}

export function CustomWorldEntityCatalog({
  profile,
  previewCharacters,
  activeTab,
  onActiveTabChange,
  onEditTarget,
  onProfileChange,
  onDeleteStoryNpcs,
  onDeleteLandmarks,
  createActionLabel,
  onCreateAction,
  createActionDisabled = false,
  openingCgGenerating = false,
  openingCgPhaseLabel = null,
  openingCgGenerateDisabled = false,
  onGenerateOpeningCg,
  pendingGeneratedEntity = null,
  recentGeneratedIds = {
    playable: [],
    story: [],
    landmark: [],
  },
  readOnly = false,
}: CustomWorldEntityCatalogProps) {
  const scrollContainerRef = useRef<HTMLDivElement | null>(null);
  const [searchDraft, setSearchDraft] = useState('');
  const [bulkDeleteMode, setBulkDeleteMode] = useState<BulkDeleteTab | null>(
    null,
  );
  const [selectedBulkIds, setSelectedBulkIds] = useState<string[]>([]);
  const [confirmState, setConfirmState] =
    useState<EntityCatalogConfirmState | null>(null);
  const deferredSearch = useDeferredValue(searchDraft.trim());

  const storyNpcById = useMemo(
    () => new Map(profile.storyNpcs.map((npc) => [npc.id, npc])),
    [profile.storyNpcs],
  );
  const roleById = useMemo(
    () =>
      new Map(
        [...profile.playableNpcs, ...profile.storyNpcs].map((role) => [
          role.id,
          role,
        ]),
      ),
    [profile.playableNpcs, profile.storyNpcs],
  );
  const landmarkById = useMemo(
    () => new Map(profile.landmarks.map((landmark) => [landmark.id, landmark])),
    [profile.landmarks],
  );
  const scenePresentations = useMemo(
    () => buildCustomWorldScenePresentations(profile),
    [profile],
  );
  const previewCharacterById = useMemo(
    () =>
      new Map(
        profile.playableNpcs.map((role, index) => [
          role.id,
          previewCharacters[index] ?? null,
        ]),
      ),
    [previewCharacters, profile.playableNpcs],
  );
  const recentPlayableIdSet = useMemo(
    () => new Set(recentGeneratedIds.playable),
    [recentGeneratedIds.playable],
  );
  const recentStoryIdSet = useMemo(
    () => new Set(recentGeneratedIds.story),
    [recentGeneratedIds.story],
  );
  const recentLandmarkIdSet = useMemo(
    () => new Set(recentGeneratedIds.landmark),
    [recentGeneratedIds.landmark],
  );

  const filteredPlayable = useMemo(
    () =>
      profile.playableNpcs.filter(
        (role) =>
          !deferredSearch ||
          matchText(buildRoleSearchText(role), deferredSearch),
      ),
    [deferredSearch, profile.playableNpcs],
  );

  const filteredStory = useMemo(
    () =>
      profile.storyNpcs.filter(
        (npc) =>
          !deferredSearch ||
          matchText(buildRoleSearchText(npc), deferredSearch),
      ),
    [deferredSearch, profile.storyNpcs],
  );

  const structuredFoundationEntries = useMemo(
    () => buildCustomWorldFoundationEntries(profile),
    [profile],
  );
  const normalizedCreatorIntent = useMemo(
    () => normalizeCustomWorldCreatorIntent(profile.creatorIntent),
    [profile.creatorIntent],
  );
  const attributeSlots = Array.isArray(profile.attributeSchema?.slots)
    ? profile.attributeSchema.slots
    : [];
  const filteredSceneEntries = useMemo(() => {
    const openingSceneEntry = {
      ...scenePresentations.camp,
      sceneTaskDescription: buildSceneTaskDescriptionText(
        scenePresentations.camp.sceneChapters,
      ),
      searchText: [
        buildOpeningSceneSearchText(profile, scenePresentations.camp),
        buildSceneChapterSearchText(
          scenePresentations.camp.sceneChapters,
          roleById,
        ),
      ]
        .filter(Boolean)
        .join(' '),
    };
    const landmarkEntries = scenePresentations.landmarks.map((scene) => {
      const landmark = profile.landmarks.find((entry) => entry.id === scene.id);

      return {
        ...scene,
        sceneTaskDescription: buildSceneTaskDescriptionText(
          scene.sceneChapters,
        ),
        searchText: [
          landmark
            ? buildLandmarkSearchText(landmark, storyNpcById, landmarkById)
            : '',
          buildSceneChapterSearchText(scene.sceneChapters, roleById),
        ]
          .filter(Boolean)
          .join(' '),
      };
    });
    const recentEntries = landmarkEntries.filter((entry) =>
      recentLandmarkIdSet.has(entry.id),
    );
    const restEntries = landmarkEntries.filter(
      (entry) => !recentLandmarkIdSet.has(entry.id),
    );
    const allEntries = [...recentEntries, openingSceneEntry, ...restEntries];

    if (!deferredSearch) {
      return allEntries;
    }

    return allEntries.filter((entry) =>
      matchText(entry.searchText, deferredSearch),
    );
  }, [
    deferredSearch,
    landmarkById,
    profile,
    recentLandmarkIdSet,
    roleById,
    scenePresentations,
    storyNpcById,
  ]);

  const lockedCharacterNames = useMemo(
    () =>
      new Set(
        normalizedCreatorIntent?.keyCharacters
          .filter((entry) => entry.locked)
          .map((entry) => entry.name.trim())
          .filter(Boolean) ?? [],
      ),
    [normalizedCreatorIntent],
  );

  const counts = {
    world: 1,
    playable:
      profile.playableNpcs.length +
      (pendingGeneratedEntity?.kind === 'playable' ? 1 : 0),
    story:
      profile.storyNpcs.length +
      (pendingGeneratedEntity?.kind === 'story' ? 1 : 0),
    landmarks:
      profile.landmarks.length +
      1 +
      (pendingGeneratedEntity?.kind === 'landmark' ? 1 : 0),
  } satisfies Record<ResultTab, number>;
  const resultTabItems = useMemo(
    () =>
      RESULT_TABS.map((tab) => ({
        id: tab.id,
        ariaLabel: `${tab.label} ${counts[tab.id]}`,
        label: (
          <div className="text-left">
            <div className="font-semibold">{tab.label}</div>
            <div className="mt-1 text-[10px] tracking-[0.16em] text-[var(--platform-text-soft)]">
              {counts[tab.id]}
            </div>
          </div>
        ),
      })),
    [counts],
  );
  const worldStatItems = [
    { label: '可扮演角色', value: profile.playableNpcs.length },
    { label: '场景角色', value: profile.storyNpcs.length },
    { label: '场景', value: profile.landmarks.length + 1 },
  ];
  const bulkDeleteTab: BulkDeleteTab | null =
    activeTab === 'story' || activeTab === 'landmarks' ? activeTab : null;
  const isBulkDeleteMode =
    bulkDeleteTab !== null && bulkDeleteMode === bulkDeleteTab;

  useEffect(() => {
    if (bulkDeleteMode && bulkDeleteMode !== activeTab) {
      setBulkDeleteMode(null);
      setSelectedBulkIds([]);
    }
  }, [activeTab, bulkDeleteMode]);

  useEffect(() => {
    const container = scrollContainerRef.current;
    if (!container) return;
    if (typeof container.scrollTo === 'function') {
      container.scrollTo({ top: 0, behavior: 'auto' });
      return;
    }
    container.scrollTop = 0;
  }, [activeTab]);

  const removePlayable = (id: string, name: string) => {
    if (profile.playableNpcs.length <= 1) {
      setConfirmState({ kind: 'minimum-playable' });
      return;
    }
    setConfirmState({ kind: 'delete-playable', id, name });
  };

  const startBulkDelete = (tab: BulkDeleteTab) => {
    setBulkDeleteMode(tab);
    setSelectedBulkIds([]);
  };

  const cancelBulkDelete = () => {
    setBulkDeleteMode(null);
    setSelectedBulkIds([]);
  };

  const toggleBulkSelected = (id: string) => {
    setSelectedBulkIds((current) =>
      current.includes(id)
        ? current.filter((entry) => entry !== id)
        : [...current, id],
    );
  };

  const confirmBulkDelete = () => {
    if (!bulkDeleteTab || selectedBulkIds.length === 0) {
      return;
    }

    const label = bulkDeleteTab === 'story' ? '场景角色' : '场景';
    setConfirmState({
      kind: 'bulk-delete',
      tab: bulkDeleteTab,
      ids: selectedBulkIds,
      label,
    });
  };

  const closeConfirmDialog = () => {
    setConfirmState(null);
  };

  const executeConfirmAction = () => {
    if (!confirmState) {
      return;
    }

    if (confirmState.kind === 'minimum-playable') {
      closeConfirmDialog();
      return;
    }

    if (confirmState.kind === 'delete-playable') {
      onProfileChange({
        ...profile,
        playableNpcs: profile.playableNpcs.filter(
          (role) => role.id !== confirmState.id,
        ),
      });
      closeConfirmDialog();
      return;
    }

    if (confirmState.tab === 'story') {
      onDeleteStoryNpcs?.(confirmState.ids);
    } else {
      onDeleteLandmarks?.(confirmState.ids);
    }
    cancelBulkDelete();
    closeConfirmDialog();
  };

  const confirmDialogConfig = (() => {
    if (!confirmState || confirmState.kind === 'minimum-playable') {
      return null;
    }

    if (confirmState.kind === 'delete-playable') {
      return {
        title: '删除角色',
        confirmLabel: '确认删除',
        body: `确认删除可扮演角色「${confirmState.name}」吗？`,
      };
    }

    return {
      title: '批量删除',
      confirmLabel: '确认删除',
      body: `确认批量删除 ${confirmState.ids.length} 个${confirmState.label}吗？`,
    };
  })();

  return (
    <div
      ref={scrollContainerRef}
      className="h-full min-h-0 space-y-3 overflow-y-auto overscroll-contain pr-1 scrollbar-hide xl:space-y-4 xl:pr-2 2xl:space-y-5 2xl:pr-3"
    >
      <div className="px-1 pb-1 text-center xl:flex xl:items-end xl:justify-between xl:gap-6 xl:rounded-[2rem] xl:border xl:border-[var(--platform-subpanel-border)] xl:bg-white/55 xl:px-6 xl:py-3 xl:text-left xl:shadow-[0_18px_70px_rgba(112,57,30,0.08)] xl:backdrop-blur-sm 2xl:px-7">
        <div className="text-[11px] font-bold tracking-[0.28em] text-zinc-500">
          世界档案
        </div>
        <div className="min-w-0 xl:flex xl:flex-1 xl:items-end xl:justify-between xl:gap-5">
          <div className="mt-2 truncate text-3xl font-black text-[var(--platform-text-strong)] sm:text-[2.2rem] xl:mt-0 xl:text-[2rem] 2xl:text-[2.25rem]">
            {profile.name}
          </div>
          <div className="mt-2 min-w-0 text-sm tracking-[0.18em] text-zinc-400 xl:mt-0 xl:max-w-[34rem] xl:truncate xl:text-right xl:text-xs">
            {profile.subtitle}
          </div>
        </div>
      </div>

      <div className="platform-sticky-fade sticky top-0 z-10 -mx-1 space-y-3 px-1 pb-3 pt-1 backdrop-blur-sm xl:rounded-[1.75rem] xl:border xl:border-[var(--platform-subpanel-border)] xl:bg-white/70 xl:px-4 xl:py-3 xl:shadow-[0_16px_48px_rgba(112,57,30,0.08)]">
        <PlatformSegmentedTabs
          items={resultTabItems}
          activeId={activeTab}
          onChange={onActiveTabChange}
          layout="scroll"
          gap="md"
          frame="bare"
          surface="transparent"
          size="sm"
          tone="neutral"
          semantics="tabs"
          ariaLabel="世界实体目录"
          className="pb-1 xl:pb-0"
          itemClassName={(_, active) =>
            [
              'platform-tab shrink-0 !min-h-0 !rounded-full !px-3 !py-2 xl:min-w-[5.25rem] xl:!px-4 xl:!py-2',
              active ? 'platform-tab--active' : null,
            ]
              .filter(Boolean)
              .join(' ')
          }
        />

        {activeTab !== 'world' ? (
          <div className="flex flex-col gap-2 sm:flex-row sm:items-center xl:gap-3">
            <div className="min-w-0 flex-1">
              <SearchBox
                value={searchDraft}
                onChange={setSearchDraft}
                placeholder={getSearchPlaceholder(activeTab)}
              />
            </div>
            <div className="flex flex-wrap items-center justify-end gap-2">
              {isBulkDeleteMode ? (
                <>
                  <PlatformPillBadge tone="neutral" size="xs">
                    已选 {selectedBulkIds.length}
                  </PlatformPillBadge>
                  <SmallButton onClick={cancelBulkDelete}>取消</SmallButton>
                  <SmallButton onClick={confirmBulkDelete} tone="rose">
                    删除选中
                  </SmallButton>
                </>
              ) : (
                <>
                  {!readOnly && createActionLabel && onCreateAction ? (
                    <SmallButton
                      onClick={onCreateAction}
                      tone="sky"
                      disabled={createActionDisabled}
                    >
                      {createActionLabel}
                    </SmallButton>
                  ) : null}
                  {!readOnly &&
                  bulkDeleteTab &&
                  ((bulkDeleteTab === 'story' && onDeleteStoryNpcs) ||
                    (bulkDeleteTab === 'landmarks' && onDeleteLandmarks)) ? (
                    <SmallButton
                      onClick={() => startBulkDelete(bulkDeleteTab)}
                      tone="rose"
                    >
                      批量删除
                    </SmallButton>
                  ) : null}
                </>
              )}
            </div>
          </div>
        ) : null}
      </div>

      {activeTab === 'world' ? (
        <div className="space-y-3 xl:grid xl:grid-cols-[minmax(18rem,0.82fr)_minmax(0,1fr)_minmax(24rem,1.08fr)] xl:items-start xl:gap-3 xl:space-y-0 2xl:gap-4">
          <Section title="档案规模">
            <PlatformStatGrid
              items={worldStatItems}
              columns="three"
              density="compact"
              surface="plain"
              itemClassName="platform-subpanel rounded-xl py-3"
              className="text-[11px] text-zinc-300"
            />
          </Section>

          <Section title="开局 CG">
            <OpeningCgPreview
              openingCg={profile.openingCg}
              isGenerating={openingCgGenerating}
              phaseLabel={openingCgPhaseLabel}
              generateDisabled={openingCgGenerateDisabled}
              readOnly={readOnly}
              onGenerate={onGenerateOpeningCg}
            />
          </Section>

          <Section
            title="世界概述"
            actions={
              readOnly ? (
                <SmallButton
                  onClick={() => onEditTarget({ kind: 'world' })}
                  tone="sky"
                >
                  查看详情
                </SmallButton>
              ) : (
                <SmallButton
                  onClick={() => onEditTarget({ kind: 'world' })}
                  tone="sky"
                >
                  编辑
                </SmallButton>
              )
            }
          >
            <div className="space-y-3 text-sm leading-7 text-zinc-300">
              <p>{profile.summary}</p>
              <PlatformStatusMessage
                tone="warning"
                surface="platform"
                size="sm"
                className="rounded-2xl py-3"
              >
                主线目标：{profile.playerGoal}
              </PlatformStatusMessage>
              <PlatformSubpanel
                as="div"
                radius="md"
                padding="sm"
                className="rounded-2xl px-3 py-3 text-zinc-300"
              >
                世界基调：{profile.tone}
              </PlatformSubpanel>
            </div>
          </Section>

          <Section
            title="基本设定"
            className="xl:col-span-3"
            actions={
              readOnly ? (
                <SmallButton
                  onClick={() => onEditTarget({ kind: 'foundation' })}
                  tone="sky"
                >
                  查看详情
                </SmallButton>
              ) : (
                <SmallButton
                  onClick={() => onEditTarget({ kind: 'foundation' })}
                  tone="sky"
                >
                  编辑
                </SmallButton>
              )
            }
          >
            <div className="space-y-3">
              <PlatformSubpanel
                as="div"
                title="角色维度"
                radius="md"
                padding="md"
                bodyClassName="mt-3 grid grid-cols-2 gap-2 sm:grid-cols-3 xl:grid-cols-6"
                className="rounded-2xl px-4 py-4"
              >
                {attributeSlots.map((slot) => (
                  <PlatformSubpanel
                    key={slot.slotId}
                    as="div"
                    surface="dark"
                    radius="xs"
                    padding="sm"
                    className="bg-black/15"
                  >
                    <div className="text-sm font-semibold text-white">
                      {slot.name}
                    </div>
                  </PlatformSubpanel>
                ))}
              </PlatformSubpanel>
              <div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
                {structuredFoundationEntries.map((entry) => (
                  <PlatformSubpanel
                    key={entry.id}
                    as="div"
                    title={entry.label}
                    radius="md"
                    padding="md"
                    className="rounded-2xl px-4 py-4"
                    bodyClassName={
                      entry.value ? 'mt-3 flex flex-wrap gap-2' : ''
                    }
                  >
                    {entry.value ? (
                      parseFoundationTagText(entry.value).map((tag, index) => (
                        <PlatformPillBadge
                          key={`${entry.id}-${index}-${tag}`}
                          tone="darkSoft"
                          size="sm"
                          className="leading-5"
                        >
                          {tag}
                        </PlatformPillBadge>
                      ))
                    ) : (
                      <div className="mt-2 text-sm leading-7 text-zinc-100">
                        待补充
                      </div>
                    )}
                  </PlatformSubpanel>
                ))}
              </div>
            </div>
          </Section>
        </div>
      ) : null}

      {activeTab === 'playable' ? (
        <div className="space-y-3 xl:grid xl:grid-cols-3 xl:gap-3 xl:space-y-0 2xl:grid-cols-4 2xl:gap-4">
          {pendingGeneratedEntity?.kind === 'playable' ? (
            <PendingEntityCard
              title={pendingGeneratedEntity.title}
              phaseLabel={pendingGeneratedEntity.phaseLabel}
              progress={pendingGeneratedEntity.progress}
            />
          ) : null}
          {filteredPlayable.length === 0 ? (
            <EmptyState title="当前没有符合搜索条件的可扮演角色。" />
          ) : (
            filteredPlayable.map((role, index) => {
              const previewCharacter =
                previewCharacterById.get(role.id) ?? null;
              const previewImageSrc = resolvePlayableRolePreviewImage(
                role,
                previewCharacter,
              );
              const description = buildPlayableRoleCardDescription(role);

              return (
                <div
                  key={buildFallbackRenderKey(
                    role.id,
                    `playable-role-${index}-${role.name.trim() || 'unnamed'}`,
                  )}
                  className="space-y-2"
                >
                  <CatalogCard
                    title={role.name}
                    description={description || '暂无描述'}
                    badge={
                      recentPlayableIdSet.has(role.id) ? <NewBadge /> : null
                    }
                    isSelectionMode={false}
                    isSelected={false}
                    layout="compact"
                    mediaClassName="h-[4.75rem] w-[4.75rem] sm:h-[5.25rem] sm:w-[5.25rem] xl:h-[5.75rem] xl:w-[5.75rem]"
                    onClick={() =>
                      onEditTarget({
                        kind: 'playable',
                        mode: 'edit',
                        id: role.id,
                      })
                    }
                    media={
                      role.imageSrc?.trim() ? (
                        <ResolvedAssetImage
                          src={role.imageSrc}
                          alt={role.name}
                          className="h-full w-full object-cover object-top"
                        />
                      ) : previewCharacter ? (
                        <CharacterAnimator
                          state={AnimationState.RUN}
                          character={previewCharacter}
                          className="h-full w-full"
                          imageClassName="object-bottom"
                        />
                      ) : previewImageSrc ? (
                        <ResolvedAssetImage
                          src={previewImageSrc}
                          alt={role.name}
                          className="h-full w-full object-cover object-top"
                        />
                      ) : (
                        <div className="flex h-full w-full items-center justify-center bg-[rgba(255,255,255,0.64)] px-3 text-center text-xs font-semibold tracking-[0.16em] text-[var(--platform-text-soft)]">
                          {role.name.slice(0, 4) || '角色'}
                        </div>
                      )
                    }
                  />
                  <div className="flex flex-wrap items-center gap-2 px-1">
                    {lockedCharacterNames.has(role.name.trim()) ? (
                      <PlatformPillBadge tone="warning" size="xxs">
                        陶泥儿主锁定
                      </PlatformPillBadge>
                    ) : null}
                    <PlatformPillBadge tone="neutral" size="xxs">
                      初始好感 {role.initialAffinity}
                    </PlatformPillBadge>
                    {role.generatedVisualAssetId ? (
                      <PlatformPillBadge tone="success" size="xxs">
                        已生成主图
                      </PlatformPillBadge>
                    ) : null}
                    {role.tags.slice(0, 2).map((tag) => (
                      <PlatformPillBadge
                        key={`${role.id}-${tag}`}
                        tone="neutral"
                        size="xxs"
                      >
                        {tag}
                      </PlatformPillBadge>
                    ))}
                    {!readOnly ? (
                      <div className="ml-auto">
                        <SmallButton
                          onClick={() => removePlayable(role.id, role.name)}
                          tone="rose"
                        >
                          删除
                        </SmallButton>
                      </div>
                    ) : null}
                  </div>
                </div>
              );
            })
          )}
        </div>
      ) : null}

      {activeTab === 'story' ? (
        <div className="space-y-3 xl:grid xl:grid-cols-3 xl:gap-3 xl:space-y-0 2xl:grid-cols-4 2xl:gap-4">
          {pendingGeneratedEntity?.kind === 'story' ? (
            <PendingEntityCard
              title={pendingGeneratedEntity.title}
              phaseLabel={pendingGeneratedEntity.phaseLabel}
              progress={pendingGeneratedEntity.progress}
            />
          ) : null}
          {filteredStory.length === 0 ? (
            <EmptyState title="当前没有符合搜索条件的场景角色。" />
          ) : (
            filteredStory.map((npc, index) => (
              <div
                key={buildFallbackRenderKey(
                  npc.id,
                  `story-npc-${index}-${npc.name.trim() || 'unnamed'}`,
                )}
              >
                <CatalogCard
                  title={npc.name}
                  description={npc.description}
                  badge={recentStoryIdSet.has(npc.id) ? <NewBadge /> : null}
                  isSelectionMode={isBulkDeleteMode}
                  isSelected={selectedBulkIds.includes(npc.id)}
                  layout="compact"
                  mediaClassName="h-[4.75rem] w-[4.75rem] sm:h-[5.25rem] sm:w-[5.25rem] xl:h-[5.75rem] xl:w-[5.75rem]"
                  onClick={() =>
                    isBulkDeleteMode
                      ? toggleBulkSelected(npc.id)
                      : readOnly
                        ? onEditTarget({
                            kind: 'story',
                            mode: 'edit',
                            id: npc.id,
                          })
                        : onEditTarget({
                            kind: 'story',
                            mode: 'edit',
                            id: npc.id,
                          })
                  }
                  media={
                    <CustomWorldNpcPortrait
                      npc={npc}
                      profile={profile}
                      visual={npc.visual}
                      className="h-full w-full"
                      contentClassName="min-h-0 p-2"
                      scale={1.82}
                      preferImageSrc
                    />
                  }
                />
              </div>
            ))
          )}
        </div>
      ) : null}

      {activeTab === 'landmarks' ? (
        <div className="space-y-3 xl:grid xl:grid-cols-3 xl:gap-3 xl:space-y-0 2xl:grid-cols-4 2xl:gap-4">
          {pendingGeneratedEntity?.kind === 'landmark' ? (
            <PendingEntityCard
              title={pendingGeneratedEntity.title}
              phaseLabel={pendingGeneratedEntity.phaseLabel}
              progress={pendingGeneratedEntity.progress}
            />
          ) : null}
          {filteredSceneEntries.length === 0 ? (
            <EmptyState title="当前没有符合搜索条件的场景。" />
          ) : (
            filteredSceneEntries.map((scene, index) => (
              <CatalogCard
                key={buildFallbackRenderKey(
                  scene.id,
                  `scene-entry-${index}-${scene.name.trim() || scene.kind}`,
                )}
                title={scene.name}
                description={compactTextList([
                  scene.kind === 'camp'
                    ? `开局场景 · ${scene.description}`
                    : scene.description,
                  scene.sceneTaskDescription,
                ]).join(' / ')}
                badge={
                  scene.kind === 'landmark' &&
                  recentLandmarkIdSet.has(scene.id) ? (
                    <NewBadge />
                  ) : null
                }
                isSelectionMode={scene.kind === 'landmark' && isBulkDeleteMode}
                isSelected={
                  scene.kind === 'landmark' &&
                  selectedBulkIds.includes(scene.id)
                }
                onClick={() =>
                  scene.kind === 'camp'
                    ? onEditTarget({ kind: 'camp' })
                    : isBulkDeleteMode
                      ? toggleBulkSelected(scene.id)
                      : onEditTarget({
                          kind: 'landmark',
                          mode: 'edit',
                          id: scene.id,
                        })
                }
                media={
                  <PlatformMediaFrame
                    src={scene.imageSrc}
                    alt={scene.name}
                    fallbackLabel={scene.name.slice(0, 4) || '场景'}
                    aspect="landscape"
                  />
                }
                actions={
                  <SceneActPreviewStrip
                    acts={scene.actPreviews}
                    sceneName={scene.name}
                  />
                }
                disabled={scene.kind === 'camp' && isBulkDeleteMode}
              />
            ))
          )}
        </div>
      ) : null}
      {confirmDialogConfig ? (
        <PlatformDangerConfirmDialog
          open
          title={confirmDialogConfig.title}
          onClose={closeConfirmDialog}
          onConfirm={executeConfirmAction}
          confirmLabel={confirmDialogConfig.confirmLabel}
        >
          {confirmDialogConfig.body}
        </PlatformDangerConfirmDialog>
      ) : null}
      {confirmState?.kind === 'minimum-playable' ? (
        <PlatformAcknowledgeStatusDialog
          status="error"
          title="无法删除"
          description="至少保留一个可扮演角色，才能正常进入自定义世界。"
          onClose={closeConfirmDialog}
          closeOnBackdrop={false}
        />
      ) : null}
    </div>
  );
}
