import { renderToStaticMarkup } from 'react-dom/server';
import { expect, test } from 'vitest';

import { AnimationState, type Character, type StoryMoment, type StoryOption, WorldType } from '../types';
import { AdventurePanel } from './AdventurePanel';

function createCharacter(): Character {
  return {
    id: 'hero',
    name: '沈行',
    title: '试剑客',
    description: '测试主角',
    backstory: '测试背景',
    avatar: '/hero.png',
    portrait: '/hero.png',
    assetFolder: 'hero',
    assetVariant: 'default',
    attributes: {
      strength: 10,
      agility: 10,
      intelligence: 8,
      spirit: 9,
    },
    personality: 'calm',
    skills: [],
    adventureOpenings: {},
  } as Character;
}

function createOption(functionId: string, actionText: string): StoryOption {
  return {
    functionId,
    actionText,
    text: actionText,
    visuals: {
      playerAnimation: AnimationState.IDLE,
      playerMoveMeters: 0,
      playerOffsetY: 0,
      playerFacing: 'right',
      scrollWorld: false,
      monsterChanges: [],
    },
  };
}

function renderPanel(
  currentStory: StoryMoment,
  displayedOptions: StoryOption[],
  overrides: {
    canRefreshOptions?: boolean;
    hideOptions?: boolean;
    isLoading?: boolean;
    onSubmitNpcChatInput?: (input: string) => boolean;
    onExitNpcChat?: () => boolean;
  } = {},
) {
  return renderToStaticMarkup(
    <AdventurePanel
      aiError={null}
      currentStory={currentStory}
      isLoading={overrides.isLoading ?? false}
      displayedOptions={displayedOptions}
      hideOptions={overrides.hideOptions ?? false}
      canRefreshOptions={overrides.canRefreshOptions ?? false}
      onRefreshOptions={() => undefined}
      onChoice={() => undefined}
      onSubmitNpcChatInput={overrides.onSubmitNpcChatInput}
      onExitNpcChat={overrides.onExitNpcChat}
      onOpenCharacter={() => undefined}
      onOpenInventory={() => undefined}
      playerCharacter={createCharacter()}
      worldType={WorldType.WUXIA}
      quests={[]}
      questUi={{
        acknowledgeQuestCompletion: () => undefined,
        claimQuestReward: () => null,
      }}
      npcChatQuestOfferUi={{
        replacePendingOffer: async () => false,
        abandonPendingOffer: () => false,
        acceptPendingOffer: () => null,
      }}
      goalStack={{
        northStarGoal: null,
        activeGoal: null,
        immediateStepGoal: null,
        supportGoals: [],
      }}
      goalPulse={null}
      onDismissGoalPulse={() => undefined}
      battleRewardUi={{
        reward: null,
        dismiss: () => undefined,
      }}
      playerHp={100}
      playerMaxHp={100}
      playerMana={20}
      playerMaxMana={20}
      playerSkillCooldowns={{}}
      inBattle={false}
      currentNpcBattleMode={null}
      statistics={{
        playTimeMs: 0,
        hostileNpcsDefeated: 0,
        questsAccepted: 0,
        questsCompleted: 0,
        questsTurnedIn: 0,
        itemsUsed: 0,
        scenesTraveled: 0,
        currentSceneName: '竹林古道',
        playerCurrency: 0,
        inventoryItemCount: 0,
        inventoryStackCount: 0,
        activeCompanionCount: 0,
        rosterCompanionCount: 0,
      }}
      musicVolume={0.6}
      onMusicVolumeChange={() => undefined}
      onSaveAndExit={() => undefined}
    />,
  );
}

test('adventure panel recognizes story_continue_adventure by function id instead of action text', () => {
  const continueOption = createOption('story_continue_adventure', '查看后续');
  const currentStory: StoryMoment = {
    text: '你们交换完这一轮判断。',
    options: [continueOption],
    deferredOptions: [createOption('idle_explore_forward', '继续向前探索')],
  };

  const html = renderPanel(currentStory, [continueOption]);

  expect(html).toContain('剧情推理完成，继续后显示新的冒险选项');
});

test('adventure panel does not show deferred hint for non-continue options with the same text', () => {
  const misleadingOption = createOption('npc_chat', '查看后续');
  const currentStory: StoryMoment = {
    text: '你们交换完这一轮判断。',
    options: [misleadingOption],
    deferredOptions: [createOption('idle_explore_forward', '继续向前探索')],
  };

  const html = renderPanel(currentStory, [misleadingOption]);

  expect(html).not.toContain('剧情推理完成，继续后显示新的冒险选项');
});

test('adventure panel shows npc chat custom input and exit button in chat mode', () => {
  const optionA = createOption('npc_chat', '先听对方把话说完');
  const optionB = createOption('npc_chat', '顺着这个问题继续追问');
  const optionC = createOption('npc_chat', '换个更轻松的语气回应');
  const currentStory: StoryMoment = {
    text: '你们的对话正在继续。',
    displayMode: 'dialogue',
    dialogue: [
      { speaker: 'player', text: '你刚才那句话是什么意思？' },
      { speaker: 'npc', speakerName: '柳无声', text: '意思是这件事还没结束。' },
    ],
    options: [optionA, optionB, optionC],
    npcAffinityEffect: {
      eventId: 'effect-liu-1',
      npcId: 'npc-liu',
      delta: 3,
    },
    npcChatState: {
      npcId: 'npc-liu',
      npcName: '柳无声',
      turnCount: 2,
      customInputPlaceholder: '输入你想对 TA 说的话',
    },
  };

  const html = renderPanel(currentStory, [optionA, optionB, optionC], {
    canRefreshOptions: true,
    onSubmitNpcChatInput: () => true,
    onExitNpcChat: () => true,
  });

  expect(html).toContain('退出聊天');
  expect(html).toContain('输入你想对 TA 说的话');
  expect(html).toContain('发送');
  expect(html).not.toContain('换一换');
  expect(html).not.toContain('关系升温');
});

test('adventure panel hides custom input and shows quest offer actions during npc quest offer mode', () => {
  const viewOption = createOption('npc_chat_quest_offer_view', '查看任务');
  viewOption.runtimePayload = {
    npcChatQuestOfferAction: 'view',
  };
  const replaceOption = createOption('npc_chat_quest_offer_replace', '更换任务');
  replaceOption.runtimePayload = {
    npcChatQuestOfferAction: 'replace',
  };
  const abandonOption = createOption('npc_chat_quest_offer_abandon', '放弃任务');
  abandonOption.runtimePayload = {
    npcChatQuestOfferAction: 'abandon',
  };
  const currentStory: StoryMoment = {
    text: '柳无声把真正的委托说了出来。',
    displayMode: 'dialogue',
    dialogue: [
      { speaker: 'player', text: '你像是还有别的话想说。' },
      { speaker: 'npc', speakerName: '柳无声', text: '确实有一件事想正式托付给你。' },
    ],
    options: [viewOption, replaceOption, abandonOption],
    npcChatState: {
      npcId: 'npc-liu',
      npcName: '柳无声',
      turnCount: 2,
      customInputPlaceholder: '输入你想对 TA 说的话',
      pendingQuestOffer: {
        quest: {
          id: 'quest-liu-1',
          issuerNpcId: 'npc-liu',
          issuerNpcName: '柳无声',
          sceneId: 'scene-bamboo',
          title: '竹林密信',
          description: '替柳无声查清竹林中的密信来源。',
          summary: '去竹林查清密信来源。',
          objective: {
            kind: 'inspect_treasure',
            requiredCount: 1,
          },
          progress: 0,
          status: 'active',
          reward: {
            affinityBonus: 5,
            currency: 10,
            items: [],
          },
          rewardText: '完成后可获得报酬。',
        },
      },
    },
  };

  const html = renderPanel(currentStory, [viewOption, replaceOption, abandonOption], {
    onSubmitNpcChatInput: () => true,
    onExitNpcChat: () => true,
  });

  expect(html).toContain('查看任务');
  expect(html).toContain('更换任务');
  expect(html).toContain('放弃任务');
  expect(html).not.toContain('发送');
  expect(html).not.toContain('输入你想对 TA 说的话');
});

test('adventure panel renders narrative story text without italics and hides option detail text', () => {
  const option = createOption('idle_observe_signs', '观察风里残下的痕迹');
  option.detailText = '这段说明不应该继续出现在 UI 里。';
  const currentStory: StoryMoment = {
    text: '风从桥洞里灌过来，你把注意力重新放回脚下与前路。',
    options: [option],
  };

  const html = renderPanel(currentStory, [option]);

  expect(html).toContain('font-serif');
  expect(html).not.toContain('italic');
  expect(html).toContain('text-[15px]');
  expect(html).not.toContain('这段说明不应该继续出现在 UI 里。');
});
