﻿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 CustomWorldProfile,
  type SceneActBlueprint,
  type SceneChapterBlueprint,
} from '../types';
import { CharacterAnimator } from './CharacterAnimator';
import { CustomWorldNpcPortrait } from './CustomWorldNpcVisualEditor';
import { ResolvedAssetImage } from './ResolvedAssetImage';
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;
  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,
}: {
  title: string;
  subtitle?: string;
  badge?: ReactNode;
  actions?: ReactNode;
  children: ReactNode;
}) {
  return (
    <div className="platform-surface platform-surface--soft px-3.5 py-3">
      <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;
}) {
  const toneClassName =
    tone === 'sky'
      ? 'platform-button platform-button--primary'
      : tone === 'rose'
        ? 'platform-button platform-button--danger'
        : 'platform-button platform-button--ghost';

  return (
    <button
      type="button"
      onClick={onClick}
      disabled={disabled}
      className={`${toneClassName} min-h-0 rounded-full px-3 py-1 text-[11px] ${disabled ? 'cursor-not-allowed opacity-45' : ''}`}
    >
      {children}
    </button>
  );
}

function SearchBox({
  value,
  onChange,
  placeholder,
}: {
  value: string;
  onChange: (value: string) => void;
  placeholder: string;
}) {
  return (
    <div className="platform-subpanel rounded-2xl px-3 py-2">
      <input
        value={value}
        onChange={(event) => onChange(event.target.value)}
        placeholder={placeholder}
        className="w-full bg-transparent text-sm text-[var(--platform-text-strong)] outline-none placeholder:text-[var(--platform-text-soft)]"
      />
    </div>
  );
}

function ImageFrame({
  src,
  alt,
  fallbackLabel,
  tone = 'square',
}: {
  src?: string;
  alt: string;
  fallbackLabel: string;
  tone?: 'square' | 'landscape';
}) {
  return (
    <div
      className={`overflow-hidden rounded-2xl border border-[var(--platform-subpanel-border)] bg-[radial-gradient(circle_at_top,rgba(255,255,255,0.22),transparent_42%),linear-gradient(180deg,rgba(255,96,147,0.92),rgba(255,146,109,0.84))] ${tone === 'landscape' ? 'aspect-[16/9]' : 'aspect-square'}`}
    >
      {src ? (
        <ResolvedAssetImage
          src={src}
          alt={alt}
          className="h-full w-full object-cover"
        />
      ) : (
        <div className="flex h-full w-full items-center justify-center px-4 text-center text-sm font-semibold tracking-[0.18em] text-zinc-400">
          {fallbackLabel}
        </div>
      )}
    </div>
  );
}

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

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

function NewBadge() {
  return (
    <span className="platform-pill platform-pill--warm px-2.5 py-1 text-[10px] font-semibold">
      新
    </span>
  );
}

function PendingEntityCard({
  title,
  phaseLabel,
  progress,
}: {
  title: string;
  phaseLabel: string;
  progress: number;
}) {
  return (
    <div className="platform-banner platform-banner--info rounded-[1.35rem] px-4 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>
        <div className="platform-pill platform-pill--cool px-2.5 py-1 text-[10px]">
          {Math.round(progress)}%
        </div>
      </div>
      <div className="platform-progress-track mt-3 h-2.5 overflow-hidden rounded-full">
        <div
          className="h-full bg-[linear-gradient(90deg,#ff4f8b_0%,#ff8a73_100%)] transition-[width] duration-300"
          style={{ width: `${Math.max(6, Math.min(100, progress))}%` }}
        />
      </div>
    </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) => (
        <div
          key={act.id}
          className="platform-subpanel h-12 w-[5.25rem] shrink-0 overflow-hidden rounded-xl"
          title={act.title}
        >
          <ResolvedAssetImage
            src={act.imageSrc}
            alt={`${sceneName}-${act.title}`}
            className="h-full w-full object-cover"
          />
        </div>
      ))}
    </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 ? (
    <div
      className={`shrink-0 rounded-full border px-2.5 py-1 text-[10px] ${
        isSelected
          ? 'border-rose-300/25 bg-rose-500/14 text-rose-50'
          : 'platform-subpanel text-[var(--platform-text-soft)]'
      }`}
    >
      {isSelected ? '已选' : '选择'}
    </div>
  ) : null;

  if (layout === 'compact') {
    return (
      <div
        role="button"
        tabIndex={disabled ? -1 : 0}
        onClick={disabled ? undefined : onClick}
        aria-disabled={disabled}
        className={`w-full rounded-[1.3rem] border p-2.5 text-left transition-colors xl:p-3 ${
          isSelected
            ? 'border-rose-300/35 bg-rose-500/10'
            : 'platform-subpanel'
        }`}
      >
        <div className="flex items-start gap-3 xl:gap-3.5">
          <div
            className={`platform-subpanel shrink-0 overflow-hidden rounded-[1rem] ${mediaClassName ?? 'h-[4.75rem] w-[4.75rem]'}`}
          >
            {media}
          </div>
          <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>
      </div>
    );
  }

  return (
    <div
      role="button"
      tabIndex={disabled ? -1 : 0}
      onClick={disabled ? undefined : onClick}
      aria-disabled={disabled}
      className={`w-full rounded-[1.4rem] border p-3 text-left transition-colors ${
        isSelected
          ? 'border-rose-300/35 bg-rose-500/10'
          : 'platform-subpanel'
      }`}
    >
      <div className="space-y-3">
        <div
          className={`platform-subpanel overflow-hidden rounded-[1.1rem] ${mediaClassName ?? ''}`}
        >
          {media}
        </div>
        <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>
    </div>
  );
}

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';

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(' ');
}

function buildAttributeSlotSummary(
  slot: CustomWorldProfile['attributeSchema']['slots'][number],
) {
  return compactTextList([
    slot.combatUseText,
    slot.socialUseText,
    slot.explorationUseText,
  ]).join(' / ');
}

export function CustomWorldEntityCatalog({
  profile,
  previewCharacters,
  activeTab,
  onActiveTabChange,
  onEditTarget,
  onProfileChange,
  onDeleteStoryNpcs,
  onDeleteLandmarks,
  createActionLabel,
  onCreateAction,
  createActionDisabled = false,
  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 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 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) {
      window.alert('至少保留一个可扮演角色，才能正常进入自定义世界。');
      return;
    }
    if (!window.confirm(`确认删除可扮演角色「${name}」吗？`)) return;
    onProfileChange({
      ...profile,
      playableNpcs: profile.playableNpcs.filter((role) => role.id !== id),
    });
  };

  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' ? '场景角色' : '场景';
    const confirmed = window.confirm(
      `确认批量删除 ${selectedBulkIds.length} 个${label}吗？`,
    );
    if (!confirmed) {
      return;
    }

    if (bulkDeleteTab === 'story') {
      onDeleteStoryNpcs?.(selectedBulkIds);
    } else {
      onDeleteLandmarks?.(selectedBulkIds);
    }
    cancelBulkDelete();
  };

  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"
    >
      <div className="px-1 pb-1 text-center xl:rounded-[2rem] xl:border xl:border-[var(--platform-subpanel-border)] xl:bg-white/55 xl:px-6 xl:py-4 xl:text-left xl:shadow-[0_18px_70px_rgba(255,79,139,0.08)] xl:backdrop-blur-sm">
        <div className="text-[11px] font-bold tracking-[0.28em] text-zinc-500">
          世界档案
        </div>
        <div className="mt-2 text-3xl font-black text-[var(--platform-text-strong)] sm:text-[2.2rem] xl:mt-1 xl:text-[2rem]">
          {profile.name}
        </div>
        <div className="mt-2 text-sm tracking-[0.18em] text-zinc-400 xl:mt-1 xl:text-xs">
          {profile.subtitle}
        </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(255,79,139,0.08)]">
        <div className="flex gap-2 overflow-x-auto pb-1 scrollbar-hide xl:pb-0">
          {RESULT_TABS.map((tab) => (
            <div key={tab.id}>
              <button
                type="button"
                onClick={() => onActiveTabChange(tab.id)}
                className={`platform-tab px-3 py-2 text-left text-sm xl:min-w-[5.25rem] xl:px-4 xl:py-2 ${activeTab === tab.id ? 'platform-tab--active' : ''}`}
              >
                <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>
              </button>
            </div>
          ))}
        </div>

        {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 ? (
                <>
                  <div className="platform-pill platform-pill--neutral px-3 py-1 text-[11px]">
                    已选 {selectedBulkIds.length}
                  </div>
                  <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-[0.8fr_1.2fr] xl:items-start xl:gap-3 xl:space-y-0">
          <Section title="档案规模">
            <div className="grid grid-cols-3 gap-2 text-center text-[11px] text-zinc-300">
              <div className="platform-subpanel rounded-xl px-2 py-3">
                <div className="text-xl font-black text-white">
                  {profile.playableNpcs.length}
                </div>
                <div>可扮演角色</div>
              </div>
              <div className="platform-subpanel rounded-xl px-2 py-3">
                <div className="text-xl font-black text-white">
                  {profile.storyNpcs.length}
                </div>
                <div>场景角色</div>
              </div>
              <div className="platform-subpanel rounded-xl px-2 py-3">
                <div className="text-xl font-black text-white">
                  {profile.landmarks.length + 1}
                </div>
                <div>场景</div>
              </div>
            </div>
          </Section>

          <Section
            title="角色维度"
            subtitle={profile.attributeSchema?.schemaName}
          >
            <div className="grid grid-cols-2 gap-2 sm:grid-cols-3">
              {attributeSlots.map((slot) => (
                <div
                  key={slot.slotId}
                  className="platform-subpanel rounded-xl px-3 py-3"
                >
                  <div className="text-sm font-semibold text-white">
                    {slot.name}
                  </div>
                  <div className="mt-1 line-clamp-2 text-[11px] leading-5 text-zinc-400">
                    {buildAttributeSlotSummary(slot) || slot.definition}
                  </div>
                </div>
              ))}
            </div>
          </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>
                <div className="platform-banner platform-banner--warning rounded-2xl px-3 py-3">
                  主线目标：{profile.playerGoal}
                </div>
                <div className="platform-subpanel rounded-2xl px-3 py-3">
                  世界基调：{profile.tone}
                </div>
              </div>
          </Section>

          <Section
            title="基本设定"
            actions={
              readOnly ? (
                <SmallButton
                  onClick={() => onEditTarget({ kind: 'foundation' })}
                  tone="sky"
                >
                  查看详情
                </SmallButton>
              ) : (
                <SmallButton
                  onClick={() => onEditTarget({ kind: 'foundation' })}
                  tone="sky"
                >
                  编辑
                </SmallButton>
              )
            }
          >
            <div className="space-y-3">
              <div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
                {structuredFoundationEntries.map((entry) => (
                  <div
                    key={entry.id}
                    className="platform-subpanel rounded-2xl px-4 py-4"
                  >
                    <div className="text-[11px] font-bold tracking-[0.18em] text-zinc-500">
                      {entry.label}
                    </div>
                    {entry.value ? (
                      <div className="mt-3 flex flex-wrap gap-2">
                        {parseFoundationTagText(entry.value).map((tag, index) => (
                          <span
                            key={`${entry.id}-${index}-${tag}`}
                            className="rounded-full border border-white/10 bg-white/[0.06] px-3 py-1 text-xs leading-5 text-zinc-100"
                          >
                            {tag}
                          </span>
                        ))}
                      </div>
                    ) : (
                      <div className="mt-2 text-sm leading-7 text-zinc-100">
                        待补充
                      </div>
                    )}
                  </div>
                ))}
              </div>
            </div>
          </Section>
        </div>
      ) : null}

      {activeTab === 'playable' ? (
        <div className="space-y-3 xl:grid xl:grid-cols-2 xl:gap-3 xl:space-y-0 2xl:grid-cols-3">
          {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()) ? (
                      <span className="platform-pill platform-pill--warm px-2.5 py-1 text-[10px]">
                        创作者锁定
                      </span>
                    ) : null}
                    <span className="platform-pill platform-pill--neutral px-2.5 py-1 text-[10px]">
                      初始好感 {role.initialAffinity}
                    </span>
                    {role.generatedVisualAssetId ? (
                      <span className="platform-pill platform-pill--success px-2.5 py-1 text-[10px]">
                        已生成主图
                      </span>
                    ) : null}
                    {role.tags.slice(0, 2).map((tag) => (
                      <span
                        key={`${role.id}-${tag}`}
                        className="platform-pill platform-pill--neutral px-2.5 py-1 text-[10px]"
                      >
                        {tag}
                      </span>
                    ))}
                    {!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-2 xl:gap-3 xl:space-y-0 2xl:grid-cols-3">
          {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-2 xl:gap-3 xl:space-y-0 2xl:grid-cols-3">
          {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={
                  <ImageFrame
                    src={scene.imageSrc}
                    alt={scene.name}
                    fallbackLabel={scene.name.slice(0, 4) || '场景'}
                    tone="landscape"
                  />
                }
                actions={
                  <SceneActPreviewStrip
                    acts={scene.actPreviews}
                    sceneName={scene.name}
                  />
                }
                disabled={scene.kind === 'camp' && isBulkDeleteMode}
              />
            ))
          )}
        </div>
      ) : null}
    </div>
  );
}

