import {motion} from 'motion/react';

import {getCharacterById} from '../../data/characterPresets';
import {getFacingTowardPlayer, MONSTERS_BY_WORLD} from '../../data/hostileNpcs';
import {RESOLVED_ENTITY_X_METERS} from '../../data/sceneEncounterPreviews';
import {
  AnimationState,
  type Character,
  type CompanionRenderState,
  type Encounter,
  type SceneHostileNpc,
  type ScenePresetInfo,
  type WorldType,
} from '../../types';
import {HostileNpcAnimator} from '../HostileNpcAnimator';
import {MedievalNpcAnimator} from '../MedievalNpcAnimator';
import {getRenderableNpcFacing} from '../npcRenderUtils';
import {ResolvedAssetImage} from '../ResolvedAssetImage';
import {NpcAffinityEffectBadge} from './NpcAffinityEffectBadge';
import {
  DialogueBubbleIcon,
  type GameCanvasEntitySelection,
  GENERIC_NPC_SCENE_SCALE,
  getCharacterBottomOffsetPx,
  getCharacterOpponentBottom,
  getCompanionSlotOffset,
  getMonsterWorldLeft,
  getNpcCombatHpTop,
  getSceneEntityZIndex,
  HOSTILE_NPC_SCENE_BOTTOM_OFFSET_PX,
  HpBar,
  mapHostileNpcAnimationToCharacterState,
  MONSTER_RENDER_OFFSETS,
  ROLE_CHARACTER_FRAME_CLASS,
  RoleCharacterSprite,
  SCENE_TRANSITION_LOWER_COMPANION_DELAY_S,
  SCENE_TRANSITION_UPPER_COMPANION_DELAY_S,
  SceneEncounterNpcSprite,
  SceneEntityButton,
} from './GameCanvasShared';

type MonsterSpriteConfig = (typeof MONSTERS_BY_WORLD)[WorldType.WUXIA][number];

interface GameCanvasEntityLayerProps {
  companions: CompanionRenderState[];
  currentScenePreset: ScenePresetInfo | null;
  sceneTransitionToken: number;
  isSceneTransitionEntering: boolean;
  isSceneTransitionExiting: boolean;
  transitionSweepPx: number;
  sceneTransitionExitDurationS: number;
  sceneTransitionEntryDurationS: number;
  companionAnchorLeft: string;
  companionAnchorBottom: string;
  playerBottomOffsetPx: number;
  sceneTransitionPhase: 'idle' | 'exiting' | 'entering';
  inBattle: boolean;
  onEntitySelect?: ((entity: GameCanvasEntitySelection) => void) | null;
  playerLeft: string;
  playerCharacter: Character | null;
  playerHp: number;
  playerMaxHp: number;
  effectivePlayerFacing: 'left' | 'right';
  effectivePlayerAnimationState: AnimationState;
  shouldShowPlayerDialogueIcon: boolean;
  dialogueIndicator?: {
    showPlayer: boolean;
    showEncounter: boolean;
    activeSpeaker?: 'player' | 'npc' | null;
  } | null;
  npcAffinityEffect?: {
    eventId: string;
    npcId: string;
    delta: number;
  } | null;
  sceneCombatants: SceneHostileNpc[];
  monsters: MonsterSpriteConfig[];
  getHostileNpcOuterLeft: (hostileNpc: SceneHostileNpc) => string;
  groundBottom: string;
  stageLiftPx: number;
  encounter: Encounter | null;
  sideAnchor: string;
  cameraAnchorX: number;
  monsterAnchorMeters: number;
  playerX: number;
}

export function GameCanvasEntityLayer({
  companions,
  currentScenePreset,
  sceneTransitionToken,
  isSceneTransitionEntering,
  isSceneTransitionExiting,
  transitionSweepPx,
  sceneTransitionExitDurationS,
  sceneTransitionEntryDurationS,
  companionAnchorLeft,
  companionAnchorBottom,
  playerBottomOffsetPx,
  sceneTransitionPhase,
  inBattle,
  onEntitySelect = null,
  playerLeft,
  playerCharacter,
  playerHp,
  playerMaxHp,
  effectivePlayerFacing,
  effectivePlayerAnimationState,
  shouldShowPlayerDialogueIcon,
  dialogueIndicator = null,
  npcAffinityEffect = null,
  sceneCombatants,
  monsters,
  getHostileNpcOuterLeft,
  groundBottom,
  stageLiftPx,
  encounter,
  sideAnchor,
  cameraAnchorX,
  monsterAnchorMeters,
  playerX,
}: GameCanvasEntityLayerProps) {
  const shouldRenderPeacefulEncounter =
    Boolean(encounter) && (!inBattle || sceneCombatants.length === 0);

  return (
    <>
      {companions.map(companion => {
        const slotOffset = getCompanionSlotOffset(companion.slot);
        return (
          <motion.div
            key={`${companion.npcId}-${companion.recruitToken ?? 'steady'}-${sceneTransitionToken}`}
            className="absolute"
            initial={isSceneTransitionEntering ? {x: -transitionSweepPx} : false}
            animate={{x: isSceneTransitionExiting ? transitionSweepPx : 0}}
            transition={{
              duration: isSceneTransitionExiting
                ? sceneTransitionExitDurationS
                : isSceneTransitionEntering
                  ? sceneTransitionEntryDurationS
                  : 0.18,
              ease: 'linear',
              delay: isSceneTransitionEntering
                ? (companion.slot === 'upper'
                  ? SCENE_TRANSITION_UPPER_COMPANION_DELAY_S
                  : SCENE_TRANSITION_LOWER_COMPANION_DELAY_S)
                : 0,
            }}
            style={{
              left: companionAnchorLeft,
              bottom: companionAnchorBottom,
              zIndex: getSceneEntityZIndex(playerBottomOffsetPx + slotOffset.bottom),
              transition: 'left 260ms linear, bottom 180ms ease',
            }}
          >
            <div className="relative">
              <div
                className="absolute"
                style={{
                  left: `${slotOffset.left}px`,
                  bottom: `${slotOffset.bottom}px`,
                  transform: `translate(${companion.entryOffsetX ?? 0}px, ${companion.entryOffsetY ?? 0}px)`,
                  transition: companion.transitionMs
                    ? `transform ${companion.transitionMs}ms linear`
                    : undefined,
                }}
              >
                <SceneEntityButton
                  onClick={() => onEntitySelect?.({kind: 'companion', companion})}
                  ariaLabel={`查看${companion.character.name}详情`}
                  className="relative flex w-28 flex-col items-center"
                >
                  {inBattle && (
                    <div className="absolute -top-2 left-1/2 -translate-x-1/2">
                      <HpBar hp={companion.hp} maxHp={companion.maxHp} tone="emerald" />
                    </div>
                  )}
                  <div className={ROLE_CHARACTER_FRAME_CLASS}>
                    <div className={companion.hp <= 0 ? 'opacity-45 grayscale' : undefined}>
                      <RoleCharacterSprite
                        state={sceneTransitionPhase === 'idle' ? companion.animationState : AnimationState.RUN}
                        character={companion.character}
                        facing={sceneTransitionPhase === 'idle' ? (companion.facing ?? 'right') : 'right'}
                      />
                    </div>
                  </div>
                </SceneEntityButton>
              </div>
            </div>
          </motion.div>
        );
      })}

      <motion.div
        key={`player-${currentScenePreset?.id ?? 'none'}-${sceneTransitionToken}`}
        className="absolute"
        initial={isSceneTransitionEntering ? {x: -transitionSweepPx} : false}
        animate={{x: isSceneTransitionExiting ? transitionSweepPx : 0}}
        transition={{
          duration: isSceneTransitionExiting
            ? sceneTransitionExitDurationS
            : isSceneTransitionEntering
              ? sceneTransitionEntryDurationS
              : 0.18,
          ease: 'linear',
        }}
        style={{
          left: playerLeft,
          bottom: companionAnchorBottom,
          zIndex: getSceneEntityZIndex(playerBottomOffsetPx),
          transition: 'left 260ms linear, bottom 180ms ease',
        }}
      >
        <div className="relative">
          {inBattle && (
            <div className="absolute -top-2 left-1/2 -translate-x-1/2">
              <HpBar hp={playerHp} maxHp={playerMaxHp} tone="emerald" />
            </div>
          )}
          <SceneEntityButton
            onClick={playerCharacter ? () => onEntitySelect?.({kind: 'player'}) : null}
            ariaLabel={playerCharacter ? `查看${playerCharacter.name}详情` : undefined}
            className="relative block"
          >
            <div className="relative">
              <div className={ROLE_CHARACTER_FRAME_CLASS}>
                {playerCharacter && (
                  <RoleCharacterSprite
                    state={effectivePlayerAnimationState}
                    character={playerCharacter}
                    facing={effectivePlayerFacing}
                  />
                )}
              </div>
            </div>
            {shouldShowPlayerDialogueIcon && (
              <div className="absolute -top-9 right-1">
                <DialogueBubbleIcon
                  active={dialogueIndicator?.activeSpeaker === 'player'}
                  flip={effectivePlayerFacing === 'left'}
                />
              </div>
            )}
          </SceneEntityButton>
        </div>
      </motion.div>

      {sceneCombatants.map(hostileNpc => {
        const npcEncounter = hostileNpc.encounter;
        if (!npcEncounter) return null;
        const config = monsters.find(item => item.id === hostileNpc.id);
        const renderOffset = MONSTER_RENDER_OFFSETS[hostileNpc.id] ?? {x: 0, y: 0};
        const npcCharacter = npcEncounter?.characterId ? getCharacterById(npcEncounter.characterId) : null;
        const npcMonsterConfig = !npcCharacter && npcEncounter?.monsterPresetId
          ? monsters.find(item => item.id === npcEncounter.monsterPresetId) ?? config ?? null
          : null;
        const npcSceneSpriteFacing =
          npcCharacter
            ? hostileNpc.facing
            : getRenderableNpcFacing(npcEncounter, hostileNpc.facing, {medievalVisual: true});
        const npcCombatHpTop = getNpcCombatHpTop(
          npcCharacter ? npcEncounter?.characterId : null,
          npcCharacter ? null : npcEncounter?.monsterPresetId,
        );
        const hostileNpcBottomOffsetPx = npcMonsterConfig
          ? HOSTILE_NPC_SCENE_BOTTOM_OFFSET_PX
          : 0;
        const opponentBottom = npcCharacter
          ? getCharacterOpponentBottom(groundBottom, stageLiftPx, npcCharacter)
          : `calc(${groundBottom} + ${stageLiftPx}px)`;
        const entityBottom = `calc(${opponentBottom} + ${(hostileNpc.yOffset ?? 0) + hostileNpcBottomOffsetPx}px)`;
        const entityBottomOffsetPx = npcCharacter
          ? getCharacterBottomOffsetPx(
              stageLiftPx,
              npcCharacter,
              (hostileNpc.yOffset ?? 0) + hostileNpcBottomOffsetPx,
            )
          : stageLiftPx + (hostileNpc.yOffset ?? 0) + hostileNpcBottomOffsetPx;

        return (
          <div
            key={hostileNpc.id}
            className="absolute"
            style={{
              left: getHostileNpcOuterLeft(hostileNpc),
              bottom: entityBottom,
              zIndex: getSceneEntityZIndex(entityBottomOffsetPx),
              transition: 'left 260ms linear, bottom 180ms ease',
            }}
          >
            <SceneEntityButton
              onClick={() => onEntitySelect?.({kind: 'npc', encounter: npcEncounter, battleState: hostileNpc})}
              ariaLabel={`查看${hostileNpc.name}详情`}
              className="relative flex w-28 flex-col items-center"
            >
              {inBattle && (
                <div
                  className="absolute left-1/2 -translate-x-1/2"
                  style={{top: `${npcCombatHpTop}px`}}
                >
                  <HpBar hp={hostileNpc.hp} maxHp={hostileNpc.maxHp} tone="rose" />
                </div>
              )}
              <div className={ROLE_CHARACTER_FRAME_CLASS}>
                {npcCharacter ? (
                  <RoleCharacterSprite
                    state={hostileNpc.characterAnimation ?? mapHostileNpcAnimationToCharacterState(hostileNpc.animation)}
                    character={npcCharacter}
                    facing={npcSceneSpriteFacing}
                  />
                ) : npcMonsterConfig ? (
                  <div style={{transform: `translate(${renderOffset.x}px, ${renderOffset.y}px)`}}>
                    <HostileNpcAnimator
                      hostileNpc={npcMonsterConfig}
                      animation={hostileNpc.animation}
                      flip={hostileNpc.facing === 'right'}
                      className="scale-[1.82] origin-bottom"
                    />
                  </div>
                ) : (
                  <MedievalNpcAnimator
                    encounter={npcEncounter}
                    className="origin-bottom drop-shadow-[0_8px_14px_rgba(0,0,0,0.38)]"
                    facing={npcSceneSpriteFacing}
                    scale={GENERIC_NPC_SCENE_SCALE}
                  />
                )}
              </div>
              {dialogueIndicator?.showEncounter && hostileNpc.animation !== 'move' && (
                <div className="absolute -top-9 left-1">
                  <DialogueBubbleIcon
                    active={dialogueIndicator.activeSpeaker === 'npc'}
                    flip={npcSceneSpriteFacing === 'left'}
                  />
                </div>
              )}
              {/* 聊天好感变化要挂在当前角色形象上，而不是消息区里。 */}
              {npcAffinityEffect?.npcId === (npcEncounter.id ?? npcEncounter.npcName) ? (
                <NpcAffinityEffectBadge effect={npcAffinityEffect} />
              ) : null}
            </SceneEntityButton>
          </div>
        );
      })}

      {shouldRenderPeacefulEncounter &&
        (() => {
          if (!encounter) {
            return null;
          }

          const isCampCompanionEncounter =
            encounter.specialBehavior === 'initial_companion'
            || encounter.specialBehavior === 'camp_companion';
          const peacefulAnchorX = RESOLVED_ENTITY_X_METERS;
          const isPeacefulEncounterMoving =
            (!isCampCompanionEncounter && sceneTransitionPhase !== 'idle')
            || Math.abs(peacefulAnchorX - RESOLVED_ENTITY_X_METERS) > 0.01;
          const towardPeacefulPlayer = getFacingTowardPlayer(peacefulAnchorX, playerX);
          const peacefulResolvedCharacter =
            encounter.kind !== 'treasure' && encounter.characterId
              ? getCharacterById(encounter.characterId)
              : null;
          const peacefulMonsterConfig = !peacefulResolvedCharacter &&
            encounter.kind === 'npc' && encounter.monsterPresetId
              ? monsters.find(item => item.id === encounter.monsterPresetId) ?? null
              : null;
          const peacefulHostileBottomOffsetPx = peacefulMonsterConfig
            ? HOSTILE_NPC_SCENE_BOTTOM_OFFSET_PX
            : 0;
          const peacefulBottomOffsetPx = peacefulResolvedCharacter
            ? getCharacterBottomOffsetPx(stageLiftPx, peacefulResolvedCharacter)
            : stageLiftPx + peacefulHostileBottomOffsetPx;
          const peacefulNpcSpriteFacing = towardPeacefulPlayer;

          return (
            <div
              className="absolute"
              style={{
                left: getMonsterWorldLeft(
                  sideAnchor,
                  peacefulAnchorX,
                  cameraAnchorX,
                  monsterAnchorMeters,
                ),
                bottom: encounter.characterId
                  ? getCharacterOpponentBottom(
                    groundBottom,
                    stageLiftPx,
                    getCharacterById(encounter.characterId),
                  )
                  : `calc(${groundBottom} + ${stageLiftPx + peacefulHostileBottomOffsetPx}px)`,
                zIndex: getSceneEntityZIndex(peacefulBottomOffsetPx),
                transition: isCampCompanionEncounter
                  ? 'bottom 180ms ease'
                  : 'left 260ms linear, bottom 180ms ease',
              }}
            >
              <SceneEntityButton
                onClick={encounter.kind === 'npc' ? () => onEntitySelect?.({kind: 'npc', encounter}) : null}
                ariaLabel={encounter.kind === 'npc' ? `查看${encounter.npcName}详情` : undefined}
                className="relative flex w-28 flex-col items-center"
              >
                <div className={ROLE_CHARACTER_FRAME_CLASS}>
                  {encounter.kind === 'treasure' ? (
                    <div className="flex h-20 w-20 items-center justify-center rounded-2xl border border-amber-400/30 bg-amber-500/15 shadow-[0_0_20px_rgba(255,255,255,0.12)]">
                      <ResolvedAssetImage
                        src={encounter.npcAvatar || '/Icons/47_treasure.png'}
                        alt={encounter.npcName}
                        className="h-12 w-12 object-contain"
                        style={{imageRendering: 'pixelated'}}
                      />
                    </div>
                  ) : peacefulResolvedCharacter &&
                    !encounter.visual &&
                    !encounter.imageSrc?.trim() ? (
                    <RoleCharacterSprite
                      state={AnimationState.IDLE}
                      character={peacefulResolvedCharacter}
                      facing={peacefulNpcSpriteFacing}
                    />
                  ) : peacefulMonsterConfig ? (
                    <HostileNpcAnimator
                      hostileNpc={peacefulMonsterConfig}
                      animation={isPeacefulEncounterMoving ? 'move' : 'idle'}
                      flip={towardPeacefulPlayer === 'right'}
                      className="scale-[1.82] origin-bottom"
                    />
                  ) : (
                    <SceneEncounterNpcSprite
                      encounter={encounter}
                      state={AnimationState.IDLE}
                      facing={peacefulNpcSpriteFacing}
                      className="drop-shadow-[0_8px_14px_rgba(0,0,0,0.38)]"
                    />
                  )}
                </div>
                {dialogueIndicator?.showEncounter && encounter.kind === 'npc' && !isPeacefulEncounterMoving && (
                  <div className="absolute -top-9 left-1">
                    <DialogueBubbleIcon
                      active={dialogueIndicator.activeSpeaker === 'npc'}
                      flip={peacefulNpcSpriteFacing === 'left'}
                    />
                  </div>
                )}
                {/* 和平相遇态同样沿用角色形象上的好感浮出特效。 */}
                {npcAffinityEffect?.npcId === (encounter.id ?? encounter.npcName) ? (
                  <NpcAffinityEffectBadge effect={npcAffinityEffect} />
                ) : null}
              </SceneEntityButton>
            </div>
          );
        })()}
    </>
  );
}
