import {
  BarChart3,
  Coins,
  Heart,
  LogOut,
  type LucideIcon,
  MapPinned,
  PackageOpen,
  ScrollText,
  Volume2,
} from 'lucide-react';
import { AnimatePresence, motion } from 'motion/react';

import { formatCurrency } from '../../data/economy';
import { getHostileNpcPresetById } from '../../data/hostileNpcPresets';
import {
  type InventoryUseEffect,
  isInventoryItemUsable,
} from '../../data/inventoryEffects';
import {
  buildInventoryItemDescription,
  getInventoryTagLabels,
} from '../../data/itemPresentation';
import { getRarityLabel } from '../../data/npcInteractions';
import { isQuestReadyToClaim } from '../../data/questFlow';
import { getScenePresetById } from '../../data/scenePresets';
import type {
  BattleRewardUi,
  QuestFlowUi,
} from '../../hooks/rpg-runtime-story';
import { sortQuestsForGoalPanel } from '../../services/storyEngine/goalDirector';
import type {
  ChapterState,
  EquipmentSlotId,
  GoalHandoff,
  GoalPulseEvent,
  GoalStackState,
  InventoryItem,
  JourneyBeat,
  QuestLogEntry,
  WorldType,
} from '../../types';
import {
  CHROME_ICONS,
  getInventoryItemVisualSrc,
  getNineSliceStyle,
  UI_CHROME,
} from '../../uiAssets';
import { HostileNpcAnimator } from '../HostileNpcAnimator';
import { PixelIcon } from '../PixelIcon';

type AdventureStatisticCard = {
  key: string;
  label: string;
  value: string;
  detail: string;
  icon: LucideIcon;
};

type AdventureStatistics = {
  playTimeMs: number;
  hostileNpcsDefeated: number;
  questsAccepted: number;
  questsCompleted: number;
  questsTurnedIn: number;
  itemsUsed: number;
  scenesTraveled: number;
  currentSceneName: string;
  playerCurrency: number;
  inventoryItemCount: number;
  inventoryStackCount: number;
  activeCompanionCount: number;
  rosterCompanionCount: number;
};

interface RpgAdventurePanelOverlaysProps {
  worldType: WorldType | null;
  quests: QuestLogEntry[];
  questUi: QuestFlowUi;
  battleRewardUi: BattleRewardUi;
  statistics: AdventureStatistics;
  statisticsCards: AdventureStatisticCard[];
  musicVolume: number;
  onMusicVolumeChange: (value: number) => void;
  onSaveAndExit: () => void;
  saveAndExitDisabled: boolean;
  isGoalPanelOpen: boolean;
  setIsGoalPanelOpen: (open: boolean) => void;
  isQuestPanelOpen: boolean;
  setIsQuestPanelOpen: (open: boolean) => void;
  isSettingsPanelOpen: boolean;
  setIsSettingsPanelOpen: (open: boolean) => void;
  isStatsPanelOpen: boolean;
  setIsStatsPanelOpen: (open: boolean) => void;
  chapterState: ChapterState | null;
  journeyBeat: JourneyBeat | null;
  goalStack: GoalStackState;
  goalPulse: GoalPulseEvent | null;
  onDismissGoalPulse: () => void;
  selectedQuest: QuestLogEntry | null;
  setSelectedQuestId: (questId: string | null) => void;
  completionNoticeQuest: QuestLogEntry | null;
  setCompletionNoticeQuestId: (questId: string | null) => void;
  rewardQuest: QuestLogEntry | null;
  setRewardQuestId: (questId: string | null) => void;
  rewardQuestHandoff: GoalHandoff | null;
  setRewardQuestHandoff: (handoff: GoalHandoff | null) => void;
  selectedRewardItemQuestId: string | null;
  setSelectedRewardItemQuestId: (questId: string | null) => void;
  selectedRewardItemId: string | null;
  setSelectedRewardItemId: (itemId: string | null) => void;
  selectedBattleRewardItemId: string | null;
  setSelectedBattleRewardItemId: (itemId: string | null) => void;
  selectedRewardItem: InventoryItem | null;
  selectedRewardUseEffect: InventoryUseEffect | null;
  selectedRewardEquipSlot: EquipmentSlotId | null;
  selectedQuestSceneName: string;
  getQuestStatusLabel: (status: QuestLogEntry['status']) => string;
  pendingNpcQuestOffer: QuestLogEntry | null;
  onAcceptPendingNpcQuestOffer: () => string | null;
}

function compactSceneTaskLabel(
  sceneName: string | null | undefined,
  fallback: string,
) {
  if (!sceneName?.trim()) {
    return fallback;
  }

  const cleaned = sceneName.replace(/[《》「」“”"']/gu, '').trim();
  return cleaned.length > 8 ? cleaned.slice(0, 8) : cleaned;
}

function formatTaskTitle(title: string, fallback = '当前任务') {
  const cleaned = title
    .replace(/[《》「」“”"']/gu, '')
    .replace(/[·｜|:：].*$/u, '')
    .replace(/[，。！？；,.!?;].*$/u, '')
    .trim();

  if (!cleaned) {
    return fallback;
  }

  return cleaned.length > 12 ? cleaned.slice(0, 10) : cleaned;
}

function buildJourneyTaskCardCopy(params: {
  beatType: JourneyBeat['beatType'] | null | undefined;
  sceneName: string;
  fallbackCondition: string;
}) {
  const { beatType, sceneName, fallbackCondition } = params;
  const sceneLabel = compactSceneTaskLabel(sceneName, '前方区域');

  switch (beatType) {
    case 'approach':
      return {
        title: `前往${sceneLabel}`,
        description: `${sceneLabel} 一带出现了值得跟进的新线索，继续靠近，看看那里到底发生了什么。`,
        condition: `前往 ${sceneName}，确认新的线索。`,
        progress: '靠近线索',
      };
    case 'investigation':
      return {
        title: `调查${sceneLabel}`,
        description: `${sceneLabel} 出现了新的异常和痕迹，继续调查，查清这里到底隐藏着什么。`,
        condition: `在 ${sceneName} 调查线索或异常。`,
        progress: '调查进行中',
      };
    case 'camp':
      return {
        title: '回营整备',
        description: '先整理队伍、资源和状态，再决定下一段任务的推进方式。',
        condition: '返回营地，整理队伍或与同伴交谈。',
        progress: '整备中',
      };
    case 'conflict':
      return {
        title: `处理${sceneLabel}`,
        description: `${sceneLabel} 的冲突已经浮出水面，需要继续推进并正面处理。`,
        condition: `在 ${sceneName} 处理当前冲突。`,
        progress: '冲突处理中',
      };
    case 'boss_prelude':
      return {
        title: `备战${sceneLabel}`,
        description: '关键战斗已经逼近，先把线索和状态准备好。',
        condition: fallbackCondition,
        progress: '战前准备',
      };
    case 'climax':
      return {
        title: `决断${sceneLabel}`,
        description: '决定结果的对峙已经临近，继续推进到最终现场。',
        condition: fallbackCondition,
        progress: '决战临近',
      };
    case 'recovery':
      return {
        title: '收束结果',
        description: '刚结束的事件还在留下影响，先整理结果，再决定下一步去向。',
        condition: fallbackCondition,
        progress: '结果收束中',
      };
    default:
      return {
        title: '继续推进',
        description: `${sceneLabel} 一带还有没查清的事，继续推进当前线索。`,
        condition: fallbackCondition,
        progress: '推进中',
      };
  }
}

function buildCurrentTaskCardCopy(params: {
  goalStack: GoalStackState;
  goalPulse: GoalPulseEvent | null;
  journeyBeat: JourneyBeat | null;
  sceneName: string;
}) {
  const { goalStack, goalPulse, journeyBeat, sceneName } = params;
  const primaryGoal = goalStack.activeGoal ?? goalStack.northStarGoal;
  const stepGoal = goalStack.immediateStepGoal ?? primaryGoal;

  if (!primaryGoal || !stepGoal) {
    return null;
  }

  if (primaryGoal.sourceKind === 'quest') {
    const description = primaryGoal.whyNow || primaryGoal.promiseText;
    return {
      eyebrow: goalPulse?.title ?? '当前任务',
      title: formatTaskTitle(primaryGoal.title, '当前任务'),
      description,
      condition: stepGoal.nextStepText,
      progress: stepGoal.progressLabel ?? primaryGoal.progressLabel ?? '推进中',
      pulseNote:
        goalPulse?.detail &&
        goalPulse.detail !== description &&
        goalPulse.detail !== stepGoal.nextStepText
          ? goalPulse.detail
          : null,
    };
  }

  const journeyCopy = buildJourneyTaskCardCopy({
    beatType: journeyBeat?.beatType,
    sceneName,
    fallbackCondition: stepGoal.nextStepText,
  });

  return {
    eyebrow: goalPulse?.title ?? '当前任务',
    title: journeyCopy.title,
    description: journeyCopy.description,
    condition: journeyCopy.condition,
    progress: journeyCopy.progress,
    pulseNote:
      goalPulse?.detail &&
      goalPulse.detail !== journeyCopy.description &&
      goalPulse.detail !== journeyCopy.condition
        ? goalPulse.detail
        : null,
  };
}

function getQuestSceneName(quest: QuestLogEntry, worldType: WorldType | null) {
  if (!quest.sceneId) {
    return '当前区域';
  }

  if (!worldType) {
    return quest.sceneId;
  }

  return getScenePresetById(worldType, quest.sceneId)?.name ?? quest.sceneId;
}

function getQuestHostileNpcName(
  quest: QuestLogEntry,
  worldType: WorldType | null,
) {
  if (!quest.objective.targetHostileNpcId) {
    return null;
  }

  return worldType
    ? (getHostileNpcPresetById(worldType, quest.objective.targetHostileNpcId)
        ?.name ?? quest.objective.targetHostileNpcId)
    : quest.objective.targetHostileNpcId;
}

function buildQuestConditionText(
  quest: QuestLogEntry,
  worldType: WorldType | null,
) {
  if (isQuestReadyToClaim(quest)) {
    return `返回找 ${quest.issuerNpcName} 交付任务并领取奖励。`;
  }

  if (quest.status === 'turned_in') {
    return '任务已经交付。';
  }

  const activeStep =
    quest.steps?.find((step) => step.id === quest.activeStepId) ??
    quest.steps?.find((step) => step.progress < step.requiredCount) ??
    null;
  const objective = activeStep ?? quest.objective;
  const sceneName = getQuestSceneName(quest, worldType);

  switch (objective.kind) {
    case 'defeat_hostile_npc':
      return `击败 ${getQuestHostileNpcName(quest, worldType) ?? '指定敌人'}。`;
    case 'inspect_treasure':
      return `在 ${sceneName} 调查宝藏或异常线索。`;
    case 'spar_with_npc':
      return `与 ${quest.issuerNpcName} 完成一场切磋。`;
    case 'talk_to_npc':
      return `返回找 ${quest.issuerNpcName} 对话。`;
    case 'reach_scene':
      return `前往 ${objective.targetSceneId ?? sceneName}。`;
    case 'deliver_item':
      return `把指定物品交给 ${quest.issuerNpcName}。`;
    default:
      return activeStep?.revealText ?? quest.summary;
  }
}

function getQuestProgressText(quest: QuestLogEntry) {
  if (isQuestReadyToClaim(quest)) {
    return '待交付';
  }

  if (quest.status === 'turned_in') {
    return '已交付';
  }

  const activeStep =
    quest.steps?.find((step) => step.id === quest.activeStepId) ??
    quest.steps?.find((step) => step.progress < step.requiredCount) ??
    null;
  const progressSource = activeStep ?? quest;
  const requiredCount =
    'requiredCount' in progressSource
      ? progressSource.requiredCount
      : quest.objective.requiredCount;

  return `${progressSource.progress}/${requiredCount}`;
}

function TaskTemplateCard({
  eyebrow,
  title,
  description,
  condition,
  progress,
  reward,
  onRewardItemSelect,
  tone = 'default',
}: {
  eyebrow: string;
  title: string;
  description: string;
  condition: string;
  progress?: string | null;
  reward?: QuestLogEntry['reward'] | null;
  onRewardItemSelect?: ((itemId: string) => void) | null;
  tone?: 'default' | 'main';
}) {
  if (!title.trim() && !description.trim() && !condition.trim()) {
    return null;
  }

  return (
    <div
      className={`rounded-2xl border px-4 py-3.5 ${
        tone === 'main'
          ? 'border-amber-300/15 bg-[radial-gradient(circle_at_top,rgba(245,158,11,0.13),transparent_65%),rgba(0,0,0,0.24)]'
          : 'border-white/8 bg-black/20'
      }`}
    >
      <div className="min-w-0">
        <div
          className={`text-[10px] tracking-[0.24em] ${tone === 'main' ? 'text-amber-200/80' : 'text-zinc-500'}`}
        >
          {eyebrow}
        </div>
        <div className="mt-2 text-sm font-semibold text-white">
          {formatTaskTitle(title)}
        </div>
      </div>

      <div className="mt-3 text-xs leading-relaxed text-zinc-400">
        <span className="text-zinc-500">任务描述：</span>
        {description}
      </div>

      <div className="mt-2 text-xs leading-relaxed text-zinc-300">
        <span className="text-zinc-500">达成条件：</span>
        {condition}
      </div>

      {progress ? (
        <div className="mt-2 text-[11px] leading-relaxed text-zinc-500">
          任务进度：{progress}
        </div>
      ) : null}

      {reward ? (
        <QuestRewardIconStrip
          reward={reward}
          onSelectItem={onRewardItemSelect ?? undefined}
        />
      ) : null}
    </div>
  );
}

function QuestRewardIconStrip({
  reward,
  onSelectItem,
}: {
  reward: QuestLogEntry['reward'];
  onSelectItem?: (itemId: string) => void;
}) {
  const hasItems = reward.items.length > 0;

  return (
    <div className="mt-3 rounded-xl border border-amber-300/10 bg-black/18 px-3 py-2">
      <div className="flex items-center justify-between gap-3">
        <div className="text-[10px] tracking-[0.2em] text-amber-200/75">
          任务奖励
        </div>
        <div className="text-[10px] text-zinc-500">
          好感 +{reward.affinityBonus}
          {(reward.experience ?? 0) > 0 ? ` · 经验 +${reward.experience}` : ''}
          {` · ${reward.currency}`}
        </div>
      </div>
      {hasItems ? (
        <div className="mt-2 flex flex-wrap gap-2">
          {reward.items.map((item) => (
            <button
              key={item.id}
              type="button"
              onClick={() => onSelectItem?.(item.id)}
              className="group relative flex h-9 w-9 items-center justify-center rounded-lg border border-white/10 bg-black/30 transition hover:border-amber-200/30"
              title={item.name}
              aria-label={`查看任务奖励 ${item.name}`}
            >
              <PixelIcon
                src={getQuestRewardItemIcon(item)}
                alt={item.name}
                className="h-6 w-6"
              />
              <span className="absolute -bottom-1 -right-1 rounded-full border border-black/40 bg-black/75 px-1 text-[9px] text-white">
                {item.quantity}
              </span>
            </button>
          ))}
        </div>
      ) : (
        <div className="mt-2 text-[11px] text-zinc-500">无物品奖励</div>
      )}
    </div>
  );
}

function GoalFocusCard({
  goalStack,
  goalPulse,
  journeyBeat,
  sceneName,
}: {
  goalStack: GoalStackState;
  goalPulse: GoalPulseEvent | null;
  journeyBeat: JourneyBeat | null;
  sceneName: string;
}) {
  const cardCopy = buildCurrentTaskCardCopy({
    goalStack,
    goalPulse,
    journeyBeat,
    sceneName,
  });

  const primaryGoal = goalStack.activeGoal ?? goalStack.northStarGoal;
  if (!cardCopy || primaryGoal?.sourceKind !== 'quest') {
    return null;
  }

  return (
    <div className="space-y-4">
      {cardCopy.pulseNote ? (
        <div className="rounded-2xl border border-white/8 bg-black/20 px-4 py-3 text-[11px] leading-relaxed text-zinc-300">
          {cardCopy.pulseNote}
        </div>
      ) : null}
      <TaskTemplateCard
        eyebrow={cardCopy.eyebrow}
        title={cardCopy.title}
        description={cardCopy.description}
        condition={cardCopy.condition}
        progress={cardCopy.progress}
        tone="main"
      />
      {(goalStack.immediateStepGoal ?? goalStack.activeGoal)?.sceneHint ||
      (goalStack.immediateStepGoal ?? goalStack.activeGoal)?.npcHint ? (
        <div className="rounded-2xl border border-white/8 bg-black/20 px-4 py-3 text-[11px] leading-relaxed text-zinc-400">
          {(goalStack.immediateStepGoal ?? goalStack.activeGoal)?.sceneHint
            ? `地点：${(goalStack.immediateStepGoal ?? goalStack.activeGoal)?.sceneHint}`
            : null}
          {(goalStack.immediateStepGoal ?? goalStack.activeGoal)?.sceneHint &&
          (goalStack.immediateStepGoal ?? goalStack.activeGoal)?.npcHint
            ? ' · '
            : null}
          {(goalStack.immediateStepGoal ?? goalStack.activeGoal)?.npcHint
            ? `相关人物：${(goalStack.immediateStepGoal ?? goalStack.activeGoal)?.npcHint}`
            : null}
        </div>
      ) : null}
    </div>
  );
}

function getQuestRewardItemIcon(item: InventoryItem) {
  return getInventoryItemVisualSrc(item);
}

function getRewardItemFrameClass(rarity: InventoryItem['rarity']) {
  switch (rarity) {
    case 'legendary':
      return 'border-amber-300/45 bg-gradient-to-br from-amber-500/18 via-orange-500/10 to-transparent shadow-[0_0_28px_rgba(245,158,11,0.12)]';
    case 'epic':
      return 'border-fuchsia-300/40 bg-gradient-to-br from-fuchsia-500/16 via-indigo-500/10 to-transparent shadow-[0_0_26px_rgba(217,70,239,0.1)]';
    case 'rare':
      return 'border-sky-300/40 bg-gradient-to-br from-sky-500/16 via-cyan-500/10 to-transparent shadow-[0_0_24px_rgba(56,189,248,0.08)]';
    case 'uncommon':
      return 'border-emerald-300/35 bg-gradient-to-br from-emerald-500/14 via-lime-500/8 to-transparent';
    default:
      return 'border-white/10 bg-white/[0.04]';
  }
}

function buildRewardItemDescription(item: InventoryItem) {
  return buildInventoryItemDescription(item);
}

function getQuestObjectivePresentation(
  quest: QuestLogEntry,
  worldType: WorldType | null,
  sceneName: string,
) {
  const hostileNpcPreset =
    worldType && quest.objective.targetHostileNpcId
      ? getHostileNpcPresetById(worldType, quest.objective.targetHostileNpcId)
      : null;

  switch (quest.objective.kind) {
    case 'defeat_hostile_npc':
      return {
        eyebrow: '悬赏目标',
        accentClass: 'from-rose-500/20 via-orange-500/10 to-transparent',
        labelClass: 'border-rose-300/25 bg-rose-500/12 text-rose-50',
        primaryLabel:
          hostileNpcPreset?.name ??
          quest.objective.targetHostileNpcId ??
          '未知敌对角色',
        secondaryLabel: sceneName,
        hostileNpcPreset,
        iconSrc: null as string | null,
      };
    case 'inspect_treasure':
      return {
        eyebrow: '宝藏踪迹',
        accentClass: 'from-amber-500/20 via-yellow-500/10 to-transparent',
        labelClass: 'border-amber-300/25 bg-amber-500/12 text-amber-50',
        primaryLabel: sceneName,
        secondaryLabel: '探索隐藏的奖励地点',
        hostileNpcPreset: null,
        iconSrc: '/Icons/47_treasure.png',
      };
    case 'spar_with_npc':
    default:
      return {
        eyebrow: '切磋会话',
        accentClass: 'from-sky-500/20 via-cyan-500/10 to-transparent',
        labelClass: 'border-sky-300/25 bg-sky-500/12 text-sky-50',
        primaryLabel: quest.issuerNpcName,
        secondaryLabel: sceneName,
        hostileNpcPreset: null,
        iconSrc: '/UI/1_weapon.png',
      };
  }
}

function RewardItemIconGrid({
  items,
  selectedItemId,
  onSelectItem,
  emptyText,
}: {
  items: InventoryItem[];
  selectedItemId: string | null;
  onSelectItem: (itemId: string) => void;
  emptyText: string;
}) {
  if (items.length === 0) {
    return (
      <div className="mt-3 rounded-xl border border-dashed border-white/10 bg-black/20 px-3 py-4 text-center text-xs text-zinc-500">
        {emptyText}
      </div>
    );
  }

  return (
    <div className="mt-3 flex flex-wrap gap-2.5">
      {items.map((item) => (
        <button
          key={item.id}
          type="button"
          onClick={() => onSelectItem(item.id)}
          aria-label={`查看奖励物品 ${item.name}`}
          title={item.name}
          className={`group relative flex h-16 w-16 items-center justify-center overflow-hidden rounded-2xl border transition hover:-translate-y-0.5 hover:border-white/20 sm:h-[4.5rem] sm:w-[4.5rem] ${getRewardItemFrameClass(
            item.rarity,
          )} ${
            selectedItemId === item.id
              ? 'ring-1 ring-amber-300/55 ring-offset-0'
              : ''
          }`}
        >
          <div className="absolute inset-0 bg-[radial-gradient(circle_at_top,rgba(255,255,255,0.12),transparent_55%)] opacity-0 transition group-hover:opacity-100" />
          <PixelIcon
            src={getQuestRewardItemIcon(item)}
            alt={item.name}
            className="relative z-[1] h-8 w-8 drop-shadow-[0_6px_10px_rgba(0,0,0,0.35)] sm:h-9 sm:w-9"
          />
          <span className="absolute bottom-1 right-1 rounded-full border border-black/35 bg-black/70 px-1.5 py-0.5 text-[10px] font-semibold text-white">
            {item.quantity}
          </span>
        </button>
      ))}
    </div>
  );
}

function QuestProgressPips({
  progress,
  total,
  activeClassName,
}: {
  progress: number;
  total: number;
  activeClassName: string;
}) {
  const safeTotal = Math.max(1, total);

  return (
    <div
      className="grid gap-1.5"
      style={{ gridTemplateColumns: `repeat(${safeTotal}, minmax(0, 1fr))` }}
    >
      {Array.from({ length: safeTotal }, (_, index) => (
        <div
          key={`quest-progress-${safeTotal}-${index}`}
          className={`h-2 rounded-full border ${
            index < progress ? activeClassName : 'border-white/8 bg-black/20'
          }`}
        />
      ))}
    </div>
  );
}

function QuestRewardGrid({
  quest,
  worldType,
  selectedItemId,
  onSelectItem,
}: {
  quest: QuestLogEntry;
  worldType: WorldType | null;
  selectedItemId: string | null;
  onSelectItem: (itemId: string) => void;
}) {
  return (
    <div className="rounded-2xl border border-amber-300/15 bg-[radial-gradient(circle_at_top,rgba(251,191,36,0.14),transparent_65%),rgba(0,0,0,0.24)] p-3">
      <div className="flex items-start justify-between gap-3">
        <div>
          <div className="text-[10px] tracking-[0.24em] text-amber-200/80">
            奖励缓存
          </div>
          <div className="mt-1 text-[11px] leading-relaxed text-zinc-500">
            点击物品图标查看详细信息。
          </div>
        </div>
        <ScrollText className="h-4 w-4 text-amber-200/70" />
      </div>

      <div className="mt-3 grid grid-cols-2 gap-2 sm:grid-cols-3">
        <div className="rounded-xl border border-rose-300/18 bg-rose-500/10 px-3 py-2.5 text-rose-50">
          <div className="flex items-center gap-2">
            <Heart className="h-4 w-4" />
            <span className="text-sm font-semibold">
              +{quest.reward.affinityBonus}
            </span>
          </div>
          <div className="mt-1 text-[10px] uppercase tracking-[0.2em] text-rose-100/70">
            好感度
          </div>
        </div>
        <div className="rounded-xl border border-amber-300/18 bg-amber-500/10 px-3 py-2.5 text-amber-50">
          <div className="flex items-center gap-2">
            <Coins className="h-4 w-4" />
            <span className="text-sm font-semibold">
              {formatCurrency(quest.reward.currency, worldType)}
            </span>
          </div>
          <div className="mt-1 text-[10px] uppercase tracking-[0.2em] text-amber-100/70">
            货币
          </div>
        </div>
        <div className="rounded-xl border border-sky-300/18 bg-sky-500/10 px-3 py-2.5 text-sky-50">
          <div className="flex items-center gap-2">
            <BarChart3 className="h-4 w-4" />
            <span className="text-sm font-semibold">
              +{quest.reward.experience ?? 0}
            </span>
          </div>
          <div className="mt-1 text-[10px] uppercase tracking-[0.2em] text-sky-100/70">
            经验
          </div>
        </div>
      </div>

      <RewardItemIconGrid
        items={quest.reward.items}
        selectedItemId={selectedItemId}
        onSelectItem={onSelectItem}
        emptyText="该任务没有物品奖励。"
      />
    </div>
  );
}

function QuestObjectiveCard({
  quest,
  worldType,
  sceneName,
  statusLabel,
}: {
  quest: QuestLogEntry;
  worldType: WorldType | null;
  sceneName: string;
  statusLabel: string;
}) {
  const presentation = getQuestObjectivePresentation(
    quest,
    worldType,
    sceneName,
  );

  return (
    <div
      className={`overflow-hidden rounded-2xl border border-white/10 bg-gradient-to-br ${presentation.accentClass}`}
    >
      <div className="flex items-center justify-between gap-3 border-b border-white/10 px-3 py-3">
        <div>
          <div className="text-[10px] tracking-[0.24em] text-white/70">
            {presentation.eyebrow}
          </div>
          <div className="mt-1 text-sm font-semibold text-white">
            {presentation.primaryLabel}
          </div>
        </div>
        <div
          className={`rounded-full border px-2.5 py-1 text-[10px] tracking-[0.16em] ${presentation.labelClass}`}
        >
          {statusLabel}
        </div>
      </div>

      <div className="grid gap-3 p-3 sm:grid-cols-[minmax(0,0.95fr)_minmax(0,1.05fr)]">
        <div className="relative overflow-hidden rounded-2xl border border-white/10 bg-black/30 p-3">
          <div className="absolute inset-x-0 bottom-0 h-12 bg-gradient-to-t from-black/40 to-transparent" />
          <div className="flex h-36 items-end justify-center">
            {presentation.hostileNpcPreset ? (
              <div className="rounded-full bg-[radial-gradient(circle,rgba(248,113,113,0.18),transparent_70%)] p-4">
                <HostileNpcAnimator
                  hostileNpc={presentation.hostileNpcPreset}
                  animation={
                    presentation.hostileNpcPreset.animations.move
                      ? 'move'
                      : 'idle'
                  }
                  className="origin-bottom scale-150 sm:scale-[1.85]"
                />
              </div>
            ) : (
              <motion.div
                animate={{ y: [0, -4, 0], scale: [1, 1.03, 1] }}
                transition={{
                  duration: 2.8,
                  ease: 'easeInOut',
                  repeat: Infinity,
                }}
                className="flex h-24 w-24 items-center justify-center rounded-3xl border border-white/10 bg-black/35"
              >
                {presentation.iconSrc && (
                  <PixelIcon
                    src={presentation.iconSrc}
                    alt={presentation.primaryLabel}
                    className="h-14 w-14"
                  />
                )}
              </motion.div>
            )}
          </div>
        </div>

        <div className="space-y-3">
          <div className="grid gap-2 sm:grid-cols-2">
            <div className="rounded-xl border border-white/10 bg-black/25 px-3 py-2.5 text-zinc-100">
              <div className="text-[10px] uppercase tracking-[0.18em] text-zinc-500">
                任务目标
              </div>
              <div className="mt-2 text-sm font-medium text-white">
                {presentation.primaryLabel}
              </div>
            </div>
            <div className="rounded-xl border border-white/10 bg-black/25 px-3 py-2.5 text-zinc-100">
              <div className="flex items-center gap-2 text-[10px] uppercase tracking-[0.18em] text-zinc-500">
                <MapPinned className="h-3.5 w-3.5" />
                区域
              </div>
              <div className="mt-2 text-sm font-medium text-white">
                {presentation.secondaryLabel}
              </div>
            </div>
          </div>

          <div className="rounded-xl border border-white/10 bg-black/25 px-3 py-3">
            <div className="flex items-center justify-between gap-3 text-[10px] uppercase tracking-[0.18em] text-zinc-500">
              <span>进度</span>
              <span className="text-white/70">
                {quest.progress}/{quest.objective.requiredCount}
              </span>
            </div>
            <div className="mt-3">
              <QuestProgressPips
                progress={quest.progress}
                total={quest.objective.requiredCount}
                activeClassName="border-emerald-300/30 bg-emerald-400/85"
              />
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

export function RpgAdventurePanelOverlays({
  worldType,
  quests,
  questUi,
  battleRewardUi,
  statistics,
  statisticsCards,
  musicVolume,
  onMusicVolumeChange,
  onSaveAndExit,
  saveAndExitDisabled,
  isGoalPanelOpen,
  setIsGoalPanelOpen,
  isQuestPanelOpen,
  setIsQuestPanelOpen,
  isSettingsPanelOpen,
  setIsSettingsPanelOpen,
  isStatsPanelOpen,
  setIsStatsPanelOpen,
  chapterState,
  journeyBeat,
  goalStack,
  goalPulse,
  onDismissGoalPulse,
  selectedQuest,
  setSelectedQuestId,
  completionNoticeQuest,
  setCompletionNoticeQuestId,
  rewardQuest,
  setRewardQuestId,
  rewardQuestHandoff,
  setRewardQuestHandoff,
  selectedRewardItemQuestId,
  setSelectedRewardItemQuestId,
  selectedRewardItemId,
  setSelectedRewardItemId,
  selectedBattleRewardItemId,
  setSelectedBattleRewardItemId,
  selectedRewardItem,
  selectedRewardUseEffect,
  selectedRewardEquipSlot,
  selectedQuestSceneName,
  getQuestStatusLabel,
  pendingNpcQuestOffer,
  onAcceptPendingNpcQuestOffer,
}: RpgAdventurePanelOverlaysProps) {
  const battleReward = battleRewardUi.reward;
  const sortedQuests = sortQuestsForGoalPanel(quests, goalStack);
  const activeGoalQuest =
    goalStack.activeGoal?.sourceKind === 'quest'
      ? (quests.find((quest) => quest.id === goalStack.activeGoal?.sourceId) ??
        null)
      : null;
  const shouldShowQuestUpdateModal = Boolean(
    activeGoalQuest && isGoalPanelOpen,
  );
  const selectQuestRewardItem = (quest: QuestLogEntry, itemId: string) => {
    setSelectedBattleRewardItemId(null);
    setSelectedRewardItemQuestId(quest.id);
    setSelectedRewardItemId(itemId);
  };
  const isPendingSelectedQuest = Boolean(
    selectedQuest && pendingNpcQuestOffer?.id === selectedQuest.id,
  );
  const closeGoalPanel = () => {
    setIsGoalPanelOpen(false);
    onDismissGoalPulse();
  };

  return (
    <>
      <AnimatePresence>
        {shouldShowQuestUpdateModal && (
          <motion.div
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            className="fixed inset-0 z-[77] flex items-center justify-center bg-black/72 p-3 backdrop-blur-sm sm:p-4"
            onClick={closeGoalPanel}
          >
            <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(84vh,34rem)] w-full max-w-md 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">
                  <div className="text-sm font-semibold text-white">
                    {goalPulse ? '任务更新' : '当前任务'}
                  </div>
                  <div className="mt-1 text-[11px] text-zinc-500">
                    只展示这一步推进最需要知道的信息
                  </div>
                </div>
                <button
                  type="button"
                  onClick={closeGoalPanel}
                  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="min-h-0 flex-1 overflow-y-auto p-4 scrollbar-hide">
                <GoalFocusCard
                  goalStack={goalStack}
                  goalPulse={goalPulse}
                  journeyBeat={journeyBeat}
                  sceneName={statistics.currentSceneName}
                />
              </div>

              <div className="flex items-center justify-end gap-2 border-t border-white/10 px-4 py-3 sm:px-5">
                {goalStack.activeGoal?.sourceKind === 'quest' ||
                goalStack.immediateStepGoal?.sourceKind === 'quest' ? (
                  <button
                    type="button"
                    onClick={() => {
                      closeGoalPanel();
                      setIsQuestPanelOpen(true);
                    }}
                    className="rounded-full border border-white/12 bg-black/20 px-3 py-1.5 text-[11px] text-zinc-200 transition hover:text-white"
                  >
                    查看任务
                  </button>
                ) : null}
                <button
                  type="button"
                  onClick={closeGoalPanel}
                  className="pixel-nine-slice pixel-pressable px-4 py-2 text-xs text-white"
                  style={getNineSliceStyle(UI_CHROME.choiceButton, {
                    paddingX: 14,
                    paddingY: 8,
                  })}
                >
                  知道了
                </button>
              </div>
            </motion.div>
          </motion.div>
        )}
      </AnimatePresence>

      <AnimatePresence>
        {isSettingsPanelOpen && (
          <motion.div
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            className="fixed inset-0 z-[75] flex items-center justify-center bg-black/72 p-3 backdrop-blur-sm sm:p-4"
            onClick={() => setIsSettingsPanelOpen(false)}
          >
            <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(88vh,36rem)] w-full max-w-md 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">
                  <div className="text-sm font-semibold text-white">
                    冒险设置
                  </div>
                  <div className="mt-1 text-[11px] text-zinc-500">
                    调整音乐音量，查看统计数据，或保存并退出。
                  </div>
                </div>
                <button
                  type="button"
                  onClick={() => setIsSettingsPanelOpen(false)}
                  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="min-h-0 flex-1 space-y-4 overflow-y-auto p-4">
                <div className="rounded-2xl border border-white/10 bg-[radial-gradient(circle_at_top,rgba(56,189,248,0.14),transparent_65%),rgba(0,0,0,0.24)] p-4">
                  <div className="flex items-start justify-between gap-3">
                    <div>
                      <div className="text-[10px] tracking-[0.24em] text-sky-200/80">
                        音频
                      </div>
                      <div className="mt-2 text-sm font-semibold text-white">
                        音乐音量
                      </div>
                    </div>
                    <Volume2 className="h-4 w-4 text-sky-200/75" />
                  </div>

                  <div className="mt-4 flex items-center gap-3">
                    <input
                      type="range"
                      min={0}
                      max={100}
                      step={1}
                      value={Math.round(musicVolume * 100)}
                      onChange={(event) =>
                        onMusicVolumeChange(
                          Number(event.currentTarget.value) / 100,
                        )
                      }
                      className="h-2 w-full cursor-pointer accent-sky-400"
                    />
                    <div className="min-w-[3rem] rounded-full border border-white/10 bg-black/30 px-2 py-1 text-center text-xs text-white">
                      {Math.round(musicVolume * 100)}%
                    </div>
                  </div>
                </div>

                <button
                  type="button"
                  onClick={() => setIsStatsPanelOpen(true)}
                  className="w-full rounded-2xl border border-white/10 bg-black/25 px-4 py-3 text-left transition hover:border-white/20"
                >
                  <div className="flex items-center justify-between gap-3">
                    <div>
                      <div className="text-sm font-semibold text-white">
                        运行统计
                      </div>
                      <div className="mt-1 text-[11px] text-zinc-500">
                        查看游戏时间、击杀数、任务和旅行历史。
                      </div>
                    </div>
                    <BarChart3 className="h-4 w-4 text-amber-200/75" />
                  </div>
                </button>

                <button
                  type="button"
                  disabled={saveAndExitDisabled}
                  onClick={() => {
                    if (saveAndExitDisabled) return;
                    setIsStatsPanelOpen(false);
                    setIsSettingsPanelOpen(false);
                    onSaveAndExit();
                  }}
                  className={`w-full rounded-2xl border px-4 py-3 text-left transition ${
                    saveAndExitDisabled
                      ? 'border-white/8 bg-black/20 text-zinc-500'
                      : 'border-rose-300/18 bg-rose-500/10 text-white hover:border-rose-300/30'
                  }`}
                >
                  <div className="flex items-center justify-between gap-3">
                    <div>
                      <div className="text-sm font-semibold">保存并退出</div>
                      <div className="mt-1 text-[11px] text-zinc-400">
                        保存当前进度并返回开始界面。
                      </div>
                    </div>
                    <LogOut className="h-4 w-4" />
                  </div>
                </button>

                {saveAndExitDisabled && (
                  <div className="rounded-xl border border-dashed border-white/10 bg-black/18 px-3 py-3 text-[11px] leading-relaxed text-zinc-500">
                    故事内容仍在加载或流式传输时，保存功能暂时禁用。
                  </div>
                )}
              </div>
            </motion.div>
          </motion.div>
        )}
      </AnimatePresence>

      <AnimatePresence>
        {isStatsPanelOpen && (
          <motion.div
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            className="fixed inset-0 z-[76] flex items-center justify-center bg-black/76 p-3 backdrop-blur-sm sm:p-4"
            onClick={() => setIsStatsPanelOpen(false)}
          >
            <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(90vh,42rem)] w-full max-w-2xl 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">
                  <div className="text-sm font-semibold text-white">
                    冒险统计
                  </div>
                  <div className="mt-1 text-[11px] text-zinc-500">
                    当前区域: {statistics.currentSceneName}
                  </div>
                </div>
                <button
                  type="button"
                  onClick={() => setIsStatsPanelOpen(false)}
                  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="min-h-0 flex-1 space-y-4 overflow-y-auto p-4">
                <div className="rounded-2xl border border-amber-300/15 bg-[radial-gradient(circle_at_top,rgba(251,191,36,0.14),transparent_65%),rgba(0,0,0,0.24)] p-4">
                  <div className="text-[10px] tracking-[0.24em] text-amber-200/80">
                    冒险总览
                  </div>
                  <div className="mt-2 text-sm leading-relaxed text-zinc-300">
                    已击败 {statistics.hostileNpcsDefeated} 个敌人，背包中有{' '}
                    {statistics.inventoryItemCount} 件物品，已探索{' '}
                    {statistics.scenesTraveled} 个场景。
                  </div>
                </div>

                <div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-4">
                  {statisticsCards.map((card) => {
                    const Icon = card.icon;

                    return (
                      <div
                        key={card.key}
                        className="rounded-2xl border border-white/10 bg-black/22 px-4 py-3"
                      >
                        <div className="flex items-center justify-between gap-3 text-zinc-400">
                          <span className="text-[10px] tracking-[0.18em]">
                            {card.label}
                          </span>
                          <Icon className="h-4 w-4" />
                        </div>
                        <div className="mt-3 text-lg font-semibold text-white">
                          {card.value}
                        </div>
                        <div className="mt-1 text-[11px] leading-relaxed text-zinc-500">
                          {card.detail}
                        </div>
                      </div>
                    );
                  })}
                </div>
              </div>
            </motion.div>
          </motion.div>
        )}
      </AnimatePresence>

      <AnimatePresence>
        {isQuestPanelOpen && (
          <motion.div
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            className="fixed inset-0 z-[68] flex items-center justify-center bg-black/72 p-3 backdrop-blur-sm sm:p-4"
            onClick={() => {
              setIsQuestPanelOpen(false);
              setSelectedQuestId(null);
            }}
          >
            <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(82vh,38rem)] w-full max-w-sm 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">
                  <div className="text-sm font-semibold text-white">
                    任务日志
                  </div>
                  <div className="mt-1 text-[11px] text-zinc-500">
                    总任务数: {quests.length}
                  </div>
                </div>
                <button
                  type="button"
                  onClick={() => {
                    setIsQuestPanelOpen(false);
                    setSelectedQuestId(null);
                  }}
                  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-1 overflow-y-auto p-3 scrollbar-hide">
                {quests.length > 0 ? (
                  <div className="space-y-2">
                    {sortedQuests.map((quest) => (
                      <div
                        key={quest.id}
                        className="rounded-2xl border border-white/8 bg-black/20 transition hover:border-white/15 focus-within:border-white/15"
                      >
                        <button
                          type="button"
                          onClick={() => setSelectedQuestId(quest.id)}
                          className="w-full px-3 pt-3 text-left focus:outline-none"
                        >
                          <div className="flex flex-wrap items-center justify-between gap-2">
                            <div className="text-[10px] tracking-[0.2em] text-zinc-500">
                              任务
                            </div>
                            <span
                              className={`rounded-full px-2 py-0.5 text-[10px] ${
                                isQuestReadyToClaim(quest)
                                  ? 'border border-emerald-400/20 bg-emerald-500/10 text-emerald-100'
                                  : quest.status === 'turned_in'
                                    ? 'border border-white/10 bg-white/8 text-zinc-200'
                                    : 'border border-sky-400/20 bg-sky-500/10 text-sky-100'
                              }`}
                            >
                              {getQuestStatusLabel(quest.status)}
                            </span>
                          </div>
                          <div className="mt-2 text-sm font-semibold text-white">
                            {formatTaskTitle(quest.title)}
                          </div>
                          <div className="mt-2 text-xs leading-relaxed text-zinc-400">
                            <span className="text-zinc-500">任务描述：</span>
                            {quest.description ||
                              quest.narrativeBinding?.playerHook ||
                              quest.summary}
                          </div>
                          <div className="mt-1 text-xs leading-relaxed text-zinc-300">
                            <span className="text-zinc-500">达成条件：</span>
                            {buildQuestConditionText(quest, worldType)}
                          </div>
                          <div className="mt-2 text-[11px] leading-relaxed text-zinc-500">
                            委托人：{quest.issuerNpcName}
                            {` · 任务进度：${getQuestProgressText(quest)}`}
                          </div>
                        </button>
                        <div className="px-3 pb-3">
                          <QuestRewardIconStrip
                            reward={quest.reward}
                            onSelectItem={(itemId) =>
                              selectQuestRewardItem(quest, itemId)
                            }
                          />
                        </div>
                      </div>
                    ))}
                  </div>
                ) : (
                  <div className="rounded-xl border border-dashed border-white/10 bg-black/20 px-4 py-6 text-center text-sm text-zinc-500">
                    暂无活跃任务。
                  </div>
                )}
              </div>
            </motion.div>
          </motion.div>
        )}
      </AnimatePresence>

      <AnimatePresence>
        {selectedQuest && (
          <motion.div
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            className="fixed inset-0 z-[69] flex items-center justify-center bg-black/72 p-3 backdrop-blur-sm sm:p-4"
            onClick={() => setSelectedQuestId(null)}
          >
            <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(86vh,48rem)] w-full max-w-2xl 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">
                  <div className="truncate text-sm font-semibold text-white">
                    {selectedQuest.title}
                  </div>
                  <div className="mt-1 text-[11px] text-zinc-500">
                    {selectedQuest.issuerNpcName}
                  </div>
                </div>
                <button
                  type="button"
                  onClick={() => setSelectedQuestId(null)}
                  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="min-h-0 flex-1 space-y-4 overflow-y-auto p-4 scrollbar-hide sm:p-5">
                <TaskTemplateCard
                  eyebrow="任务详情"
                  title={selectedQuest.title}
                  description={
                    selectedQuest.description ||
                    selectedQuest.narrativeBinding?.playerHook ||
                    selectedQuest.summary
                  }
                  condition={buildQuestConditionText(selectedQuest, worldType)}
                  progress={getQuestProgressText(selectedQuest)}
                  reward={selectedQuest.reward}
                  onRewardItemSelect={(itemId) =>
                    selectQuestRewardItem(selectedQuest, itemId)
                  }
                  tone="main"
                />

                <div className="grid gap-3 lg:grid-cols-[minmax(0,1.25fr)_minmax(0,0.75fr)]">
                  <QuestObjectiveCard
                    quest={selectedQuest}
                    worldType={worldType}
                    sceneName={selectedQuestSceneName}
                    statusLabel={
                      isPendingSelectedQuest
                        ? '待领取'
                        : getQuestStatusLabel(selectedQuest.status)
                    }
                  />
                  <QuestRewardGrid
                    quest={selectedQuest}
                    worldType={worldType}
                    selectedItemId={
                      selectedRewardItemQuestId === selectedQuest.id
                        ? selectedRewardItemId
                        : null
                    }
                    onSelectItem={(itemId) => {
                      setSelectedBattleRewardItemId(null);
                      setSelectedRewardItemQuestId(selectedQuest.id);
                      setSelectedRewardItemId(itemId);
                    }}
                  />
                </div>

                {isPendingSelectedQuest && (
                  <div className="flex justify-end">
                    <button
                      type="button"
                      onClick={() => {
                        const acceptedQuestId = onAcceptPendingNpcQuestOffer();
                        if (!acceptedQuestId) return;
                        setSelectedRewardItemQuestId(null);
                        setSelectedRewardItemId(null);
                        setSelectedBattleRewardItemId(null);
                      }}
                      className="pixel-nine-slice pixel-pressable px-4 py-2 text-xs text-white"
                      style={getNineSliceStyle(UI_CHROME.choiceButton, {
                        paddingX: 14,
                        paddingY: 8,
                      })}
                    >
                      领取任务
                    </button>
                  </div>
                )}

                {isQuestReadyToClaim(selectedQuest) &&
                  !isPendingSelectedQuest && (
                    <div className="flex justify-end">
                      <button
                        type="button"
                        onClick={() => setSelectedQuestId(null)}
                        className="pixel-nine-slice pixel-pressable px-4 py-2 text-xs text-white"
                        style={getNineSliceStyle(UI_CHROME.choiceButton, {
                          paddingX: 14,
                          paddingY: 8,
                        })}
                      >
                        返回交付
                      </button>
                    </div>
                  )}
              </div>
            </motion.div>
          </motion.div>
        )}
      </AnimatePresence>

      <AnimatePresence>
        {completionNoticeQuest && (
          <motion.div
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            className="fixed inset-0 z-[70] flex items-center justify-center bg-black/70 p-4 backdrop-blur-sm"
            onClick={() => {
              questUi.acknowledgeQuestCompletion(completionNoticeQuest.id);
              setCompletionNoticeQuestId(null);
            }}
          >
            <motion.div
              initial={{ opacity: 0, scale: 0.94, y: 10 }}
              animate={{ opacity: 1, scale: 1, y: 0 }}
              exit={{ opacity: 0, scale: 0.94, y: 10 }}
              transition={{ duration: 0.18, ease: 'easeOut' }}
              className="pixel-nine-slice pixel-modal-shell flex max-h-[min(92vh,32rem)] w-full max-w-sm 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="min-h-0 flex-1 space-y-3 overflow-y-auto px-5 py-5 text-center">
                <div className="text-[11px] tracking-[0.24em] text-emerald-300">
                  任务完成
                </div>
                <div className="text-lg font-semibold text-white">
                  奖励已准备
                </div>
                <div className="text-sm text-zinc-300">
                  {completionNoticeQuest.title}
                </div>
                <div className="rounded-xl border border-emerald-400/15 bg-emerald-500/10 px-3 py-3 text-sm text-emerald-50">
                  {goalStack.immediateStepGoal?.sourceKind === 'quest'
                    ? goalStack.immediateStepGoal.nextStepText
                    : '可前往任务日志领取奖励。'}
                </div>
                <div className="flex justify-center">
                  <button
                    type="button"
                    onClick={() => {
                      questUi.acknowledgeQuestCompletion(
                        completionNoticeQuest.id,
                      );
                      setCompletionNoticeQuestId(null);
                      setIsQuestPanelOpen(true);
                    }}
                    className="pixel-nine-slice pixel-pressable px-4 py-2 text-xs text-white"
                    style={getNineSliceStyle(UI_CHROME.choiceButton, {
                      paddingX: 14,
                      paddingY: 8,
                    })}
                  >
                    打开任务日志
                  </button>
                </div>
              </div>
            </motion.div>
          </motion.div>
        )}
      </AnimatePresence>

      <AnimatePresence>
        {rewardQuest && (
          <motion.div
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            className="fixed inset-0 z-[71] flex items-center justify-center bg-black/72 p-3 backdrop-blur-sm sm:p-4"
            onClick={() => {
              setRewardQuestId(null);
              setRewardQuestHandoff(null);
              setSelectedRewardItemId(null);
              setSelectedRewardItemQuestId(null);
            }}
          >
            <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,42rem)] w-full max-w-lg 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">
                  <div className="text-sm font-semibold text-white">
                    任务奖励已领取
                  </div>
                  <div className="mt-1 text-[11px] text-zinc-500">
                    {rewardQuest.title}
                  </div>
                </div>
                <button
                  type="button"
                  onClick={() => {
                    setRewardQuestId(null);
                    setRewardQuestHandoff(null);
                    setSelectedRewardItemId(null);
                    setSelectedRewardItemQuestId(null);
                  }}
                  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="min-h-0 flex-1 overflow-y-auto p-4">
                {rewardQuestHandoff ? (
                  <div className="mb-4 rounded-2xl border border-violet-300/15 bg-[radial-gradient(circle_at_top,rgba(139,92,246,0.14),transparent_65%),rgba(0,0,0,0.24)] p-3">
                    <div className="text-[10px] tracking-[0.24em] text-violet-200/80">
                      下一步建议
                    </div>
                    <div className="mt-2 text-sm font-semibold text-white">
                      {rewardQuestHandoff.title}
                    </div>
                    <div className="mt-2 text-sm leading-relaxed text-zinc-300">
                      {rewardQuestHandoff.detail}
                    </div>
                  </div>
                ) : null}
                <QuestRewardGrid
                  quest={rewardQuest}
                  worldType={worldType}
                  selectedItemId={
                    selectedRewardItemQuestId === rewardQuest.id
                      ? selectedRewardItemId
                      : null
                  }
                  onSelectItem={(itemId) => {
                    setSelectedBattleRewardItemId(null);
                    setSelectedRewardItemQuestId(rewardQuest.id);
                    setSelectedRewardItemId(itemId);
                  }}
                />
              </div>
            </motion.div>
          </motion.div>
        )}
      </AnimatePresence>

      <AnimatePresence>
        {battleReward && (
          <motion.div
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            className="fixed inset-0 z-[73] flex items-center justify-center bg-black/72 p-3 backdrop-blur-sm sm:p-4"
            onClick={() => {
              battleRewardUi.dismiss();
              setSelectedBattleRewardItemId(null);
            }}
          >
            <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,44rem)] w-full max-w-lg 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">
                  <div className="text-sm font-semibold text-white">
                    战斗奖励
                  </div>
                  <div className="mt-1 text-[11px] text-zinc-500">
                    已击败敌人: {battleReward.defeatedHostileNpcs.length}
                  </div>
                </div>
                <button
                  type="button"
                  onClick={() => {
                    battleRewardUi.dismiss();
                    setSelectedBattleRewardItemId(null);
                  }}
                  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="min-h-0 flex-1 space-y-4 overflow-y-auto p-4">
                <div className="rounded-2xl border border-emerald-300/15 bg-[radial-gradient(circle_at_top,rgba(16,185,129,0.14),transparent_65%),rgba(0,0,0,0.24)] p-3">
                  <div className="text-[10px] tracking-[0.24em] text-emerald-200/80">
                    战斗结束
                  </div>
                  <div className="mt-2 text-sm leading-relaxed text-zinc-300">
                    战利品已添加至背包。可查看下方掉落物品详情。
                  </div>
                  <div className="mt-3 flex flex-wrap gap-2">
                    {battleReward.defeatedHostileNpcs.map((hostileNpc) => (
                      <span
                        key={`${battleReward.id}-${hostileNpc.id}`}
                        className="rounded-full border border-emerald-300/20 bg-emerald-500/10 px-3 py-1 text-xs text-emerald-50"
                      >
                        {hostileNpc.name}
                      </span>
                    ))}
                  </div>
                </div>

                <div className="rounded-2xl border border-amber-300/15 bg-[radial-gradient(circle_at_top,rgba(251,191,36,0.14),transparent_65%),rgba(0,0,0,0.24)] p-3">
                  <div className="flex items-start justify-between gap-3">
                    <div>
                      <div className="text-[10px] tracking-[0.24em] text-amber-200/80">
                        战利品
                      </div>
                      <div className="mt-1 text-[11px] leading-relaxed text-zinc-500">
                        {battleReward.items.length > 0
                          ? '点击物品图标查看详情。'
                          : '本次战斗无可用战利品。'}
                      </div>
                    </div>
                    <PackageOpen className="h-4 w-4 text-amber-200/70" />
                  </div>

                  <RewardItemIconGrid
                    items={battleReward.items}
                    selectedItemId={selectedBattleRewardItemId}
                    onSelectItem={(itemId) => {
                      setSelectedRewardItemQuestId(null);
                      setSelectedRewardItemId(null);
                      setSelectedBattleRewardItemId(itemId);
                    }}
                    emptyText="本次战斗无战利品掉落。"
                  />
                </div>
              </div>
            </motion.div>
          </motion.div>
        )}
      </AnimatePresence>

      <AnimatePresence>
        {selectedRewardItem && (
          <motion.div
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            className="fixed inset-0 z-[74] flex items-center justify-center bg-black/72 p-3 backdrop-blur-sm sm:p-4"
            onClick={() => {
              setSelectedRewardItemId(null);
              setSelectedRewardItemQuestId(null);
              setSelectedBattleRewardItemId(null);
            }}
          >
            <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,36rem)] w-full max-w-sm 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">
                  <div className="truncate text-sm font-semibold text-white">
                    {selectedRewardItem.name}
                  </div>
                  <div className="mt-1 text-[11px] text-zinc-500">
                    {selectedRewardItem.category}
                  </div>
                </div>
                <button
                  type="button"
                  onClick={() => {
                    setSelectedRewardItemId(null);
                    setSelectedRewardItemQuestId(null);
                    setSelectedBattleRewardItemId(null);
                  }}
                  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="min-h-0 flex-1 space-y-4 overflow-y-auto p-4">
                <div className="flex items-center gap-3">
                  <div
                    className={`flex h-[4.5rem] w-[4.5rem] items-center justify-center rounded-2xl border bg-black/25 ${getRewardItemFrameClass(selectedRewardItem.rarity)}`}
                  >
                    <PixelIcon
                      src={getQuestRewardItemIcon(selectedRewardItem)}
                      className="h-10 w-10"
                    />
                  </div>
                  <div className="min-w-0 flex-1 space-y-1 text-sm text-zinc-300">
                    <div>
                      稀有度: {getRarityLabel(selectedRewardItem.rarity)}
                    </div>
                    <div>数量: {selectedRewardItem.quantity}</div>
                    <div>
                      {selectedRewardEquipSlot
                        ? `装备槽: ${selectedRewardEquipSlot}`
                        : '不可装备'}
                    </div>
                    <div>
                      {isInventoryItemUsable(selectedRewardItem)
                        ? '可直接使用'
                        : '被动/非即时生效物品'}
                    </div>
                  </div>
                </div>

                <div className="rounded-xl border border-white/8 bg-black/20 px-3 py-3 text-sm leading-relaxed text-zinc-300">
                  {buildRewardItemDescription(selectedRewardItem)}
                </div>

                {selectedRewardUseEffect && (
                  <div className="rounded-xl border border-emerald-400/15 bg-emerald-500/10 px-3 py-3 text-xs text-emerald-50">
                    效果预览：生命 +{selectedRewardUseEffect.hpRestore} / 灵力 +
                    {selectedRewardUseEffect.manaRestore} / 冷却 -
                    {selectedRewardUseEffect.cooldownReduction}
                  </div>
                )}

                <div className="rounded-xl border border-white/8 bg-black/20 px-3 py-3 text-xs text-zinc-300">
                  标签:{' '}
                  {getInventoryTagLabels(selectedRewardItem.tags).join(' / ') ||
                    '无'}
                </div>
              </div>
            </motion.div>
          </motion.div>
        )}
      </AnimatePresence>
    </>
  );
}
