import {AnimatePresence, motion} from 'motion/react';
import {lazy, Suspense, useCallback, useEffect, useMemo, useState} from 'react';

import {getLiveGamePlayTimeMs} from '../data/runtimeStats';
import type { HydratedSavedGameSnapshot } from '../persistence/runtimeSnapshotTypes';
import {getWorldCampScenePreset} from '../data/scenePresets';
import {BottomTab} from '../hooks/useGameFlow';
import {resolveActiveSceneActBlueprint} from '../services/customWorldSceneActRuntime';
import {
  type BattleRewardUi,
  type CharacterChatUi,
  type GoalFlowUi,
  type InventoryFlowUi,
  type NpcChatQuestOfferUi,
  type QuestFlowUi,
  type StoryGenerationNpcUi,
} from '../hooks/useStoryGeneration';
import {
  type Character,
  type CustomWorldProfile,
  type CompanionRenderState,
  type GameState,
  type StoryMoment,
  type StoryOption,
} from '../types';
import {CHROME_ICONS, getNineSliceStyle, TAB_ICONS, UI_CHROME} from '../uiAssets';
import {CharacterSelectionFlow} from './game-shell/CharacterSelectionFlow';
import {PreGameSelectionFlow} from './game-shell/PreGameSelectionFlow';
import {SCENE_TRANSITION_FUNCTION_MODES, useSceneTransitionModel} from './game-shell/useSceneTransitionModel';
import {useGameShellViewModel} from './game-shell/useGameShellViewModel';
import {GameCanvas} from './GameCanvas';
import {PixelIcon} from './PixelIcon';

interface GameShellSessionProps {
  gameState: GameState;
  currentStory: StoryMoment | null;
  isLoading: boolean;
  aiError: string | null;
  bottomTab: BottomTab;
  setBottomTab: (tab: BottomTab) => void;
  isMapOpen: boolean;
  setIsMapOpen: (open: boolean) => void;
}

interface GameShellStoryProps {
  displayedOptions: StoryOption[];
  canRefreshOptions: boolean;
  handleRefreshOptions: () => void;
  handleChoice: (option: StoryOption) => void;
  handleNpcChatInput: (input: string) => boolean;
  exitNpcChat: () => boolean;
  handleMapTravelToScene: (sceneId: string) => boolean;
  npcUi: StoryGenerationNpcUi;
  characterChatUi: CharacterChatUi;
  inventoryUi: InventoryFlowUi;
  battleRewardUi: BattleRewardUi;
  questUi: QuestFlowUi;
  npcChatQuestOfferUi: NpcChatQuestOfferUi;
  goalUi: GoalFlowUi;
}

interface GameShellEntryProps {
  hasSavedGame: boolean;
  savedSnapshot: HydratedSavedGameSnapshot | null;
  handleContinueGame: (snapshot?: HydratedSavedGameSnapshot | null) => void;
  handleStartNewGame: () => void;
  handleSaveAndExit: () => void;
  handleCustomWorldSelect: (customWorldProfile: CustomWorldProfile) => void;
  handleBackToWorldSelect: () => void;
  handleCharacterSelect: (character: Character) => void;
}

interface GameShellCompanionProps {
  companionRenderStates: CompanionRenderState[];
  buildCompanionRenderStates: (state: GameState) => CompanionRenderState[];
  onBenchCompanion: (npcId: string) => void;
  onActivateRosterCompanion: (npcId: string, swapNpcId?: string | null) => void;
}

interface GameShellAudioProps {
  musicVolume: number;
  onMusicVolumeChange: (value: number) => void;
}

interface GameShellProps {
  session: GameShellSessionProps;
  story: GameShellStoryProps;
  entry: GameShellEntryProps;
  companions: GameShellCompanionProps;
  audio: GameShellAudioProps;
}

const AdventureEntityModal = lazy(async () => {
  const module = await import('./AdventureEntityModal');

  return {
    default: module.AdventureEntityModal,
  };
});

const CharacterChatModal = lazy(async () => {
  const module = await import('./CharacterChatModal');

  return {
    default: module.CharacterChatModal,
  };
});

const CompanionCampModal = lazy(async () => {
  const module = await import('./CompanionCampModal');

  return {
    default: module.CompanionCampModal,
  };
});

const MapModal = lazy(async () => {
  const module = await import('./MapModal');

  return {
    default: module.MapModal,
  };
});

const NpcModals = lazy(async () => {
  const module = await import('./NpcModals');

  return {
    default: module.NpcModals,
  };
});

const AdventurePanel = lazy(async () => {
  const module = await import('./AdventurePanel');

  return {
    default: module.AdventurePanel,
  };
});

const CharacterPanel = lazy(async () => {
  const module = await import('./CharacterPanel');

  return {
    default: module.CharacterPanel,
  };
});

const InventoryPanel = lazy(async () => {
  const module = await import('./InventoryPanel');

  return {
    default: module.InventoryPanel,
  };
});

function ModalLoadingFallback({
  label,
  onClose,
}: {
  label: string;
  onClose?: (() => void) | null;
}) {
  return (
    <div
      className="fixed inset-0 z-[90] flex items-center justify-center bg-black/70 p-4 backdrop-blur-sm"
      onClick={onClose ?? undefined}
    >
      <div
        className="pixel-nine-slice pixel-modal-shell flex min-h-40 w-full max-w-md items-center justify-center px-6 py-8 text-center text-sm text-zinc-300 shadow-[0_24px_80px_rgba(0,0,0,0.55)]"
        style={getNineSliceStyle(UI_CHROME.modalPanel)}
        onClick={event => event.stopPropagation()}
      >
        {label}
      </div>
    </div>
  );
}

function PanelLoadingFallback({
  label,
}: {
  label: string;
}) {
  return (
    <div className="pixel-nine-slice flex min-h-0 flex-1 items-center justify-center px-4 py-6 text-center text-xs uppercase tracking-[0.24em] text-zinc-500" style={getNineSliceStyle(UI_CHROME.modalPanel)}>
      {label}
    </div>
  );
}

export function GameShell({session, story, entry, companions, audio}: GameShellProps) {
  const {
    gameState,
    currentStory,
    isLoading,
    aiError,
    bottomTab,
    setBottomTab,
    isMapOpen,
    setIsMapOpen,
  } = session;
  const {
    displayedOptions,
    canRefreshOptions,
    handleRefreshOptions,
    handleChoice,
    handleNpcChatInput,
    exitNpcChat,
    handleMapTravelToScene,
    npcUi,
    characterChatUi,
    inventoryUi,
    battleRewardUi,
    questUi,
    npcChatQuestOfferUi,
    goalUi,
  } = story;
  const {
    hasSavedGame,
    savedSnapshot,
    handleContinueGame,
    handleStartNewGame,
    handleSaveAndExit,
    handleCustomWorldSelect,
    handleBackToWorldSelect,
    handleCharacterSelect,
  } = entry;
  const {
    companionRenderStates,
    buildCompanionRenderStates,
    onBenchCompanion,
    onActivateRosterCompanion,
  } = companions;
  const {musicVolume, onMusicVolumeChange} = audio;

  const [clockNow, setClockNow] = useState(() => Date.now());
  const openingCampSceneId = useMemo(
    () => (gameState.worldType ? getWorldCampScenePreset(gameState.worldType)?.id ?? null : null),
    [gameState.worldType],
  );
  const hasNpcModalOpen = Boolean(npcUi.tradeModal || npcUi.giftModal || npcUi.recruitModal);
  const {
    selectionStage,
    setSelectionStage,
    overlayPanel,
    openOverlayPanel,
    closeOverlayPanel,
    selectedSceneEntity,
    setSelectedSceneEntity,
    openPartyMemberDetails,
    closeAdventureEntityModal,
    showTeamModal,
    openCampModal,
    closeCampModal,
    resetForSaveAndExit,
    shouldMountAdventureEntityModal,
    shouldMountCampModal,
    shouldMountMapModal,
    shouldMountCharacterChatModal,
    shouldMountNpcModals,
  } = useGameShellViewModel({
    gameState,
    isMapOpen,
    characterChatModalOpen: Boolean(characterChatUi.modal),
    hasNpcModalOpen,
  });
  const {
    visibleGameState,
    visibleCurrentStory,
    sceneTransitionPhase,
    sceneTransitionToken,
    setSceneTransitionDurations,
    beginSceneTransition,
  } = useSceneTransitionModel({
    gameState,
    currentStory,
    openingCampSceneId,
  });
  const isCharacterSelectionStage =
    gameState.currentScene === 'Selection' &&
    Boolean(gameState.worldType) &&
    !gameState.playerCharacter;
  const collapseTopStage = gameState.currentScene === 'Selection';
  const shouldHideStoryOptions = sceneTransitionPhase !== 'idle';
  const visibleStoryForRender = visibleCurrentStory;

  const dialogueIndicator = useMemo(() => {
    if (!isLoading || visibleCurrentStory?.displayMode !== 'dialogue' || visibleGameState.currentEncounter?.kind !== 'npc') {
      return null;
    }

    const lastSpeaker = visibleCurrentStory.dialogue?.[visibleCurrentStory.dialogue.length - 1]?.speaker ?? null;
    return {
      showPlayer: true,
      showEncounter: true,
      activeSpeaker: lastSpeaker === 'player' ? 'player' : lastSpeaker ? 'npc' : null,
    } as const;
  }, [visibleCurrentStory?.dialogue, visibleCurrentStory?.displayMode, visibleGameState.currentEncounter?.kind, isLoading]);

  const characterChatSummaries = useMemo(
    () =>
      Object.fromEntries(
        Object.entries(gameState.characterChats ?? {}).map(([characterId, record]) => [characterId, record.summary]),
      ),
    [gameState.characterChats],
  );

  const visibleCompanionRenderStates = useMemo(
    () => buildCompanionRenderStates(visibleGameState),
    [buildCompanionRenderStates, visibleGameState],
  );

  const canvasCompanionRenderStates = useMemo(() => {
    const activeEncounterNpcId = visibleGameState.currentEncounter?.kind === 'npc'
      ? visibleGameState.currentEncounter.id ?? null
      : null;
    if (!activeEncounterNpcId) return visibleCompanionRenderStates;
    return visibleCompanionRenderStates.filter(companion => companion.npcId !== activeEncounterNpcId);
  }, [visibleCompanionRenderStates, visibleGameState.currentEncounter]);

  const livePlayTimeMs = useMemo(
    () => getLiveGamePlayTimeMs(gameState.runtimeStats, clockNow),
    [clockNow, gameState.runtimeStats],
  );
  const activeSceneAct = useMemo(
    () => resolveActiveSceneActBlueprint({
      profile: visibleGameState.customWorldProfile,
      sceneId: visibleGameState.currentScenePreset?.id ?? null,
      storyEngineMemory: visibleGameState.storyEngineMemory,
    }),
    [
      visibleGameState.currentScenePreset?.id,
      visibleGameState.customWorldProfile,
      visibleGameState.storyEngineMemory,
    ],
  );
  const activeSceneChapter = useMemo(() => {
    if (!visibleGameState.customWorldProfile || !visibleGameState.currentScenePreset?.id) {
      return null;
    }

    return (
      visibleGameState.customWorldProfile.sceneChapterBlueprints?.find(
        entry => entry.sceneId === visibleGameState.currentScenePreset?.id
          || entry.linkedLandmarkIds.includes(visibleGameState.currentScenePreset?.id ?? ''),
      ) ?? null
    );
  }, [
    visibleGameState.currentScenePreset?.id,
    visibleGameState.customWorldProfile,
  ]);

  const adventureStatistics = useMemo(
    () => ({
      playTimeMs: livePlayTimeMs,
      hostileNpcsDefeated: gameState.runtimeStats.hostileNpcsDefeated,
      questsAccepted: gameState.runtimeStats.questsAccepted,
      questsCompleted: visibleGameState.quests.filter(quest => quest.status === 'completed' || quest.status === 'turned_in').length,
      questsTurnedIn: visibleGameState.quests.filter(quest => quest.status === 'turned_in').length,
      itemsUsed: gameState.runtimeStats.itemsUsed,
      scenesTraveled: gameState.runtimeStats.scenesTraveled,
      currentSceneName: visibleGameState.currentScenePreset?.name ?? 'Current Area',
      playerCurrency: visibleGameState.playerCurrency,
      inventoryItemCount: visibleGameState.playerInventory.reduce((sum, item) => sum + item.quantity, 0),
      inventoryStackCount: visibleGameState.playerInventory.length,
      activeCompanionCount: visibleGameState.companions.length,
      rosterCompanionCount: visibleGameState.roster.length,
    }),
    [
      gameState.runtimeStats.itemsUsed,
      gameState.runtimeStats.hostileNpcsDefeated,
      gameState.runtimeStats.questsAccepted,
      gameState.runtimeStats.scenesTraveled,
      livePlayTimeMs,
      visibleGameState.companions.length,
      visibleGameState.currentScenePreset?.name,
      visibleGameState.playerCurrency,
      visibleGameState.playerInventory,
      visibleGameState.quests,
      visibleGameState.roster.length,
    ],
  );

  useEffect(() => {
    if (!gameState.playerCharacter || gameState.currentScene !== 'Story') {
      return;
    }

    setClockNow(Date.now());
    const intervalId = window.setInterval(() => setClockNow(Date.now()), 1000);
    return () => window.clearInterval(intervalId);
  }, [gameState.currentScene, gameState.playerCharacter]);

  const handleSceneTransitionChoice = useCallback((option: StoryOption) => {
    const transitionMode = SCENE_TRANSITION_FUNCTION_MODES[option.functionId];
    if (transitionMode) {
      beginSceneTransition(transitionMode);
    }
    handleChoice(option);
  }, [beginSceneTransition, handleChoice]);

  return (
    <div
      className="fusion-pixel-app pixel-root-shell flex h-screen max-h-screen flex-col overflow-hidden font-sans text-zinc-100"
      style={{
        backgroundImage: `linear-gradient(rgba(8, 10, 14, 0.82), rgba(8, 10, 14, 0.82)), url("${UI_CHROME.appBackground}")`,
        backgroundPosition: 'center',
        backgroundRepeat: 'repeat',
      }}
    >
      <div className={`relative ${collapseTopStage ? 'h-0 border-b-0' : 'h-[36%] border-b border-white/5'}`}>
        {collapseTopStage ? null : (
          <GameCanvas
            scrollWorld={visibleGameState.scrollWorld}
            animationState={visibleGameState.animationState}
            playerCharacter={visibleGameState.playerCharacter}
            encounter={visibleGameState.currentEncounter}
            currentScenePreset={visibleGameState.currentScenePreset}
            worldType={visibleGameState.worldType}
            sceneHostileNpcs={visibleGameState.sceneHostileNpcs}
            playerX={visibleGameState.playerX}
            playerOffsetY={visibleGameState.playerOffsetY}
            playerFacing={visibleGameState.playerFacing}
            playerActionMode={visibleGameState.playerActionMode}
            inBattle={visibleGameState.inBattle}
            playerHp={visibleGameState.playerHp}
            playerMaxHp={visibleGameState.playerMaxHp}
            playerMana={visibleGameState.playerMana}
            playerMaxMana={visibleGameState.playerMaxMana}
            activeCombatEffects={visibleGameState.activeCombatEffects}
            companions={canvasCompanionRenderStates}
            npcStates={visibleGameState.npcStates}
            dialogueIndicator={dialogueIndicator}
            npcAffinityEffect={visibleStoryForRender?.npcAffinityEffect ?? null}
            onEntitySelect={setSelectedSceneEntity}
            onSceneNameClick={() => setIsMapOpen(true)}
            sceneTransitionPhase={sceneTransitionPhase}
            sceneTransitionToken={sceneTransitionToken}
            onSceneTransitionDurationsChange={setSceneTransitionDurations}
          />
        )}
      </div>

      <div
        className={`pixel-app-shell flex min-h-0 flex-1 flex-col ${isCharacterSelectionStage ? 'justify-center p-4 sm:p-5' : 'p-3 sm:p-4'}`}
        style={{
          backgroundColor: isCharacterSelectionStage ? '#0d1016' : undefined,
          backgroundImage: isCharacterSelectionStage
            ? undefined
            : `linear-gradient(rgba(10, 12, 18, 0.55), rgba(10, 12, 18, 0.55)), url("${UI_CHROME.appBackground}")`,
          backgroundPosition: isCharacterSelectionStage ? undefined : 'center',
          backgroundRepeat: isCharacterSelectionStage ? undefined : 'repeat',
        }}
      >
        <AnimatePresence mode="wait">
          {!gameState.worldType && (
            <PreGameSelectionFlow
              selectionStage={selectionStage}
              setSelectionStage={setSelectionStage}
              gameState={gameState}
              hasSavedGame={hasSavedGame}
              savedSnapshot={savedSnapshot}
              handleContinueGame={handleContinueGame}
              handleStartNewGame={handleStartNewGame}
              handleCustomWorldSelect={handleCustomWorldSelect}
            />
          )}

          {gameState.worldType && !gameState.playerCharacter && (
            <motion.div
              key="character-select-shell"
              initial={{opacity: 0, y: 12}}
              animate={{opacity: 1, y: 0}}
              exit={{opacity: 0, y: -12}}
              className="flex h-full min-h-0 flex-col"
            >
              <CharacterSelectionFlow
                worldType={gameState.worldType}
                customWorldProfile={gameState.customWorldProfile}
                onBack={() => {
                  handleBackToWorldSelect();
                  setSelectionStage('platform');
                }}
                onConfirm={handleCharacterSelect}
              />
            </motion.div>
          )}

          {visibleGameState.playerCharacter && visibleStoryForRender && (
            <motion.div key="story-flow" initial={{opacity: 0}} animate={{opacity: 1}} className="flex h-full min-h-0 flex-col">
              <div className="story-top-tabs mb-3 grid grid-cols-3 gap-2 sm:gap-3">
                <button
                  onClick={() => setBottomTab('character')}
                  className={`pixel-nine-slice pixel-pressable pixel-tab-button ${bottomTab === 'character' ? 'pixel-tab-button--active text-white' : 'text-zinc-300'}`}
                  style={getNineSliceStyle(bottomTab === 'character' ? UI_CHROME.tabActive : UI_CHROME.tabInactive, {paddingX: 10, paddingY: 8})}
                >
                  <span className="pixel-tab-button__inner">
                    <PixelIcon
                      src={bottomTab === 'character' ? TAB_ICONS.character.active : TAB_ICONS.character.inactive}
                      className={`pixel-tab-button__icon ${bottomTab === 'character' ? 'opacity-100' : 'opacity-70'}`}
                    />
                    <span className="pixel-tab-button__label">队伍</span>
                  </span>
                </button>
                <button
                  onClick={() => setBottomTab('adventure')}
                  className={`pixel-nine-slice pixel-pressable pixel-tab-button ${bottomTab === 'adventure' ? 'pixel-tab-button--active text-white' : 'text-zinc-300'}`}
                  style={getNineSliceStyle(bottomTab === 'adventure' ? UI_CHROME.tabActive : UI_CHROME.tabInactive, {paddingX: 10, paddingY: 8})}
                >
                  <span className="pixel-tab-button__inner">
                    <PixelIcon
                      src={bottomTab === 'adventure' ? TAB_ICONS.adventure.active : TAB_ICONS.adventure.inactive}
                      className={`pixel-tab-button__icon ${bottomTab === 'adventure' ? 'opacity-100' : 'opacity-70'}`}
                    />
                    <span className="pixel-tab-button__label">冒险</span>
                  </span>
                </button>
                <button
                  onClick={() => setBottomTab('inventory')}
                  className={`pixel-nine-slice pixel-pressable pixel-tab-button ${bottomTab === 'inventory' ? 'pixel-tab-button--active text-white' : 'text-zinc-300'}`}
                  style={getNineSliceStyle(bottomTab === 'inventory' ? UI_CHROME.tabActive : UI_CHROME.tabInactive, {paddingX: 10, paddingY: 8})}
                >
                  <span className="pixel-tab-button__inner">
                    <PixelIcon
                      src={bottomTab === 'inventory' ? TAB_ICONS.inventory.active : TAB_ICONS.inventory.inactive}
                      className={`pixel-tab-button__icon ${bottomTab === 'inventory' ? 'opacity-100' : 'opacity-70'}`}
                    />
                    <span className="pixel-tab-button__label">背包</span>
                  </span>
                </button>
              </div>

              {bottomTab === 'character' && (
                <Suspense fallback={<PanelLoadingFallback label="正在加载队伍面板" />}>
                  <CharacterPanel
                    worldType={visibleGameState.worldType}
                    customWorldProfile={visibleGameState.customWorldProfile}
                    playerCharacter={visibleGameState.playerCharacter}
                    playerHp={visibleGameState.playerHp}
                    playerMaxHp={visibleGameState.playerMaxHp}
                    playerMana={visibleGameState.playerMana}
                    playerMaxMana={visibleGameState.playerMaxMana}
                    playerEquipment={visibleGameState.playerEquipment}
                    activeBuildBuffs={visibleGameState.activeBuildBuffs}
                    companionRenderStates={companionRenderStates}
                    npcStates={visibleGameState.npcStates}
                    quests={visibleGameState.quests}
                    companionArcStates={
                      visibleGameState.storyEngineMemory?.companionArcStates ?? []
                    }
                    companionResolutions={
                      visibleGameState.storyEngineMemory?.companionResolutions ?? []
                    }
                    onOpenCamp={openCampModal}
                    onOpenCharacterChat={characterChatUi.openChat}
                    chatSummaries={characterChatSummaries}
                    onInspectMember={openPartyMemberDetails}
                  />
                </Suspense>
              )}

              {bottomTab === 'adventure' && (
                <Suspense fallback={<PanelLoadingFallback label="正在加载冒险面板" />}>
                  <AdventurePanel
                    aiError={aiError}
                    currentStory={visibleStoryForRender}
                    isLoading={isLoading}
                    displayedOptions={displayedOptions}
                    hideOptions={shouldHideStoryOptions}
                    canRefreshOptions={canRefreshOptions}
                    onRefreshOptions={handleRefreshOptions}
                    onChoice={handleSceneTransitionChoice}
                    onSubmitNpcChatInput={handleNpcChatInput}
                    onExitNpcChat={exitNpcChat}
                    onOpenCharacter={() => openOverlayPanel('character')}
                    onOpenInventory={() => openOverlayPanel('inventory')}
                    playerCharacter={visibleGameState.playerCharacter}
                    worldType={visibleGameState.worldType}
                    quests={visibleGameState.quests}
                    questUi={questUi}
                    npcChatQuestOfferUi={npcChatQuestOfferUi}
                    goalStack={goalUi.goalStack}
                    goalPulse={goalUi.pulse}
                    onDismissGoalPulse={goalUi.dismissPulse}
                    battleRewardUi={battleRewardUi}
                    playerHp={visibleGameState.playerHp}
                    playerMaxHp={visibleGameState.playerMaxHp}
                    playerMana={visibleGameState.playerMana}
                    playerMaxMana={visibleGameState.playerMaxMana}
                    playerSkillCooldowns={visibleGameState.playerSkillCooldowns}
                    inBattle={visibleGameState.inBattle}
                    currentNpcBattleMode={visibleGameState.currentNpcBattleMode}
                    chapterState={visibleGameState.chapterState ?? null}
                    journeyBeat={
                      visibleGameState.storyEngineMemory?.currentJourneyBeat ?? null
                    }
                    currentSceneActTitle={activeSceneAct?.title ?? null}
                    currentSceneActIndex={
                      activeSceneChapter && activeSceneAct
                        ? (() => {
                            const actIndex = activeSceneChapter.acts.findIndex(
                              act => act.id === activeSceneAct.id,
                            );
                            return actIndex >= 0 ? actIndex + 1 : null;
                          })()
                        : null
                    }
                    currentSceneActCount={activeSceneChapter?.acts.length ?? null}
                    statistics={adventureStatistics}
                    musicVolume={musicVolume}
                    onMusicVolumeChange={onMusicVolumeChange}
                    onSaveAndExit={() => {
                      resetForSaveAndExit();
                      handleSaveAndExit();
                    }}
                  />
                </Suspense>
              )}

              {bottomTab === 'inventory' && (
                <Suspense fallback={<PanelLoadingFallback label="正在加载背包面板" />}>
                  <InventoryPanel
                    playerCharacter={visibleGameState.playerCharacter}
                    worldType={visibleGameState.worldType}
                    playerInventory={visibleGameState.playerInventory}
                    playerCurrency={visibleGameState.playerCurrency}
                    playerHp={visibleGameState.playerHp}
                    playerMaxHp={visibleGameState.playerMaxHp}
                    playerMana={visibleGameState.playerMana}
                    playerMaxMana={visibleGameState.playerMaxMana}
                    inBattle={visibleGameState.inBattle}
                    onUseItem={inventoryUi.useInventoryItem}
                    onEquipItem={inventoryUi.equipInventoryItem}
                    forgeRecipes={inventoryUi.forgeRecipes}
                    onCraftRecipe={inventoryUi.craftRecipe}
                    onDismantleItem={inventoryUi.dismantleItem}
                    onReforgeItem={inventoryUi.reforgeItem}
                    continueGameDigest={
                      visibleGameState.storyEngineMemory?.continueGameDigest ?? null
                    }
                    narrativeCodex={
                      visibleGameState.storyEngineMemory?.narrativeCodex ?? []
                    }
                    narrativeQaReport={
                      visibleGameState.storyEngineMemory?.narrativeQaReport ?? null
                    }
                  />
                </Suspense>
              )}
            </motion.div>
          )}
        </AnimatePresence>
      </div>

      {shouldMountAdventureEntityModal && (
        <Suspense fallback={<ModalLoadingFallback label="正在加载冒险详情..." onClose={closeAdventureEntityModal} />}>
          <AdventureEntityModal
            selection={selectedSceneEntity}
            gameState={gameState}
            onClose={closeAdventureEntityModal}
            onOpenCharacterChat={target => {
              closeAdventureEntityModal();
              characterChatUi.openChat(target);
            }}
          />
        </Suspense>
      )}

      <AnimatePresence>
        {overlayPanel && gameState.playerCharacter && (
          <motion.div
            initial={{opacity: 0}}
            animate={{opacity: 1}}
            exit={{opacity: 0}}
            className="fixed inset-0 z-[65] flex items-center justify-center bg-black/70 p-4 backdrop-blur-sm"
            onClick={closeOverlayPanel}
          >
            <motion.div
              initial={{opacity: 0, scale: 0.96, y: 8}}
              animate={{opacity: 1, scale: 1, y: 0}}
              exit={{opacity: 0, scale: 0.96, y: 8}}
              transition={{duration: 0.18, ease: 'easeOut'}}
              className="pixel-nine-slice pixel-modal-shell flex max-h-[min(92vh,60rem)] w-full max-w-5xl flex-col overflow-hidden shadow-[0_24px_80px_rgba(0,0,0,0.55)]"
              style={getNineSliceStyle(UI_CHROME.modalPanel)}
              onClick={event => event.stopPropagation()}
            >
              <div className="relative border-b border-white/10 px-4 py-3 sm:px-5 sm:py-4">
                <div className="min-w-0 pr-10 text-sm font-semibold text-white">{overlayPanel === 'character' ? '队伍' : '背包'}</div>
                <button
                  type="button"
                  onClick={closeOverlayPanel}
                  className="absolute right-4 top-3 p-1 text-zinc-400 transition-colors hover:text-white sm:right-5 sm:top-4"
                >
                  <PixelIcon src={CHROME_ICONS.close} className="h-4 w-4" />
                </button>
              </div>
              <div className="flex min-h-0 flex-1 p-5">
                {overlayPanel === 'character' ? (
                  <Suspense fallback={<PanelLoadingFallback label="正在加载队伍面板" />}>
                    <CharacterPanel
                      worldType={gameState.worldType}
                      customWorldProfile={gameState.customWorldProfile}
                      playerCharacter={gameState.playerCharacter}
                      playerHp={gameState.playerHp}
                      playerMaxHp={gameState.playerMaxHp}
                      playerMana={gameState.playerMana}
                      playerMaxMana={gameState.playerMaxMana}
                      playerEquipment={gameState.playerEquipment}
                      activeBuildBuffs={gameState.activeBuildBuffs}
                      companionRenderStates={companionRenderStates}
                      npcStates={gameState.npcStates}
                      quests={gameState.quests}
                      onOpenCamp={() => {
                        closeOverlayPanel();
                        openCampModal();
                      }}
                      onOpenCharacterChat={target => {
                        closeOverlayPanel();
                        characterChatUi.openChat(target);
                      }}
                      chatSummaries={characterChatSummaries}
                      onInspectMember={openPartyMemberDetails}
                    />
                  </Suspense>
                ) : (
                  <Suspense fallback={<PanelLoadingFallback label="正在加载背包面板" />}>
                    <InventoryPanel
                      playerCharacter={gameState.playerCharacter}
                      worldType={gameState.worldType}
                      playerInventory={gameState.playerInventory}
                      playerCurrency={gameState.playerCurrency}
                      playerHp={gameState.playerHp}
                      playerMaxHp={gameState.playerMaxHp}
                      playerMana={gameState.playerMana}
                      playerMaxMana={gameState.playerMaxMana}
                      inBattle={gameState.inBattle}
                      onUseItem={inventoryUi.useInventoryItem}
                      onEquipItem={inventoryUi.equipInventoryItem}
                      forgeRecipes={inventoryUi.forgeRecipes}
                      onCraftRecipe={inventoryUi.craftRecipe}
                      onDismantleItem={inventoryUi.dismantleItem}
                      onReforgeItem={inventoryUi.reforgeItem}
                    />
                  </Suspense>
                )}
              </div>
            </motion.div>
          </motion.div>
        )}
      </AnimatePresence>

      {shouldMountCampModal && (
        <Suspense fallback={<ModalLoadingFallback label="正在加载队伍营地..." onClose={closeCampModal} />}>
          <CompanionCampModal
            isOpen={showTeamModal}
            playerCharacter={gameState.playerCharacter}
            companions={gameState.companions}
            roster={gameState.roster}
            inBattle={gameState.inBattle}
            onClose={closeCampModal}
            onBenchCompanion={onBenchCompanion}
            onActivateCompanion={onActivateRosterCompanion}
          />
        </Suspense>
      )}

      {shouldMountMapModal && (
        <Suspense fallback={<ModalLoadingFallback label="正在加载地图..." onClose={() => setIsMapOpen(false)} />}>
          <MapModal
            isOpen={isMapOpen}
            currentScenePreset={gameState.currentScenePreset}
            worldType={gameState.worldType}
            canTravel={!gameState.inBattle && !isLoading}
            onTravelToScene={scene => {
              const triggered = handleMapTravelToScene(scene.id);
              if (triggered) {
                setIsMapOpen(false);
              }
            }}
            isTraveling={isLoading}
            onClose={() => setIsMapOpen(false)}
          />
        </Suspense>
      )}

      {shouldMountCharacterChatModal && (
        <Suspense fallback={<ModalLoadingFallback label="正在加载角色聊天..." onClose={characterChatUi.closeChat} />}>
          <CharacterChatModal
            modal={characterChatUi.modal}
            onClose={characterChatUi.closeChat}
            onDraftChange={characterChatUi.setDraft}
            onUseSuggestion={characterChatUi.useSuggestion}
            onRefreshSuggestions={characterChatUi.refreshSuggestions}
            onSendDraft={characterChatUi.sendDraft}
          />
        </Suspense>
      )}

      {shouldMountNpcModals && (
        <Suspense fallback={<ModalLoadingFallback label="正在加载场景角色交互..." />}>
          <NpcModals gameState={gameState} npcUi={npcUi} />
        </Suspense>
      )}
    </div>
  );
}
