import React, {useEffect, useState} from 'react';

import {getCharacterById} from '../../data/characterPresets';
import {METERS_TO_PIXELS} from '../../data/hostileNpcs';
import {buildMedievalNpcVisualFromCustomWorldVisual} from '../../data/medievalNpcVisuals';
import {
  AnimationState,
  Character,
  CombatActionMode,
  CombatVisualEffect,
  CompanionRenderState,
  Encounter,
  SceneHostileNpc,
  ScenePresetInfo,
  WorldType,
} from '../../types';
import {CharacterAnimator} from '../CharacterAnimator';
import {MedievalNpcAnimator} from '../MedievalNpcAnimator';

export type GameCanvasEntitySelection =
  | {kind: 'player'}
  | {kind: 'companion'; companion: CompanionRenderState}
  | {kind: 'npc'; encounter: Encounter; battleState?: SceneHostileNpc};

export interface GameCanvasProps {
  scrollWorld: boolean;
  animationState: AnimationState;
  playerCharacter: Character | null;
  encounter: Encounter | null;
  currentScenePreset: ScenePresetInfo | null;
  worldType: WorldType | null;
  sceneHostileNpcs: SceneHostileNpc[];
  playerX: number;
  playerOffsetY: number;
  playerFacing: 'left' | 'right';
  playerActionMode?: CombatActionMode;
  inBattle: boolean;
  playerHp: number;
  playerMaxHp: number;
  playerMana?: number;
  playerMaxMana?: number;
  activeCombatEffects?: CombatVisualEffect[];
  companions?: CompanionRenderState[];
  npcStates?: unknown;
  dialogueIndicator?: {
    showPlayer: boolean;
    showEncounter: boolean;
    activeSpeaker?: 'player' | 'npc' | null;
  } | null;
  onEntitySelect?: ((entity: GameCanvasEntitySelection) => void) | null;
  onSceneNameClick?: (() => void) | null;
  sceneTransitionPhase?: 'idle' | 'exiting' | 'entering';
  sceneTransitionToken?: number;
  onSceneTransitionDurationsChange?: ((durations: {exitMs: number; entryMs: number}) => void) | null;
}

export const MONSTER_RENDER_OFFSETS: Record<string, {x: number; y: number}> = {
  'monster-06': {x: -18, y: 14},
};
export const ENTITY_CONTAINER_REM = 7;
export const ROLE_CHARACTER_FRAME_CLASS = 'flex h-28 w-28 items-end justify-center overflow-visible';
export const ROLE_CHARACTER_SPRITE_CLASS = 'h-full w-full scale-[1.32] origin-bottom';
export const GENERIC_NPC_SCENE_SCALE = 1.72;
export const DEFAULT_COMBAT_HP_TOP_PX = -18;
export const CHARACTER_NPC_COMBAT_HP_TOP_PX = -2;
export const GENERIC_NPC_COMBAT_HP_TOP_PX = 94;
export const GENERIC_NPC_EFFECT_TARGET_OFFSET_PX = -16;
export const HOSTILE_NPC_SCENE_INSET_PX = 28;
export const HOSTILE_NPC_SCENE_BOTTOM_OFFSET_PX = -18;
export const CHAT_BUBBLE_SPRITE_SRC = '/chat.png';
export const CHAT_BUBBLE_FRAME_WIDTH = 27;
export const CHAT_BUBBLE_FRAME_HEIGHT = 22;
export const CHAT_BUBBLE_FRAME_COUNT = 12;
export const CHAT_BUBBLE_ACTIVE_FRAMES = [0, 1, 2, 3, 4, 5];
export const CHAT_BUBBLE_INACTIVE_FRAMES = [6, 7, 8, 9, 10, 11];
export const SCENE_TITLE_GEAR_FILTER =
  'sepia(1) saturate(2.1) hue-rotate(338deg) brightness(0.94) contrast(1.08) drop-shadow(0 6px 12px rgba(0, 0, 0, 0.42))';
export const SCENE_TRANSITION_SPRITE_CLEARANCE_PX = 168;
export const SCENE_TRANSITION_REFERENCE_STAGE_WIDTH_PX = 400;
export const SCENE_TRANSITION_REFERENCE_DURATION_S = 5;
export const SCENE_TRANSITION_SPEED_PX_PER_S =
  (SCENE_TRANSITION_REFERENCE_STAGE_WIDTH_PX + SCENE_TRANSITION_SPRITE_CLEARANCE_PX)
  / SCENE_TRANSITION_REFERENCE_DURATION_S;
export const SCENE_TRANSITION_UPPER_COMPANION_DELAY_S = 0.43;
export const SCENE_TRANSITION_LOWER_COMPANION_DELAY_S = 0.93;

export function getCompanionSlotOffset(slot: CompanionRenderState['slot']) {
  return slot === 'upper'
    ? {left: -56, bottom: 66}
    : {left: -34, bottom: 10};
}

export function mapHostileNpcAnimationToCharacterState(animation: SceneHostileNpc['animation']) {
  if (animation === 'move') return AnimationState.RUN;
  if (animation === 'attack') return AnimationState.ATTACK;
  return AnimationState.IDLE;
}

export function HpBar({
  hp,
  maxHp,
  tone,
}: {
  hp: number;
  maxHp: number;
  tone: 'emerald' | 'rose';
}) {
  const ratio = Math.max(0, Math.min(1, maxHp > 0 ? hp / maxHp : 0));
  const fill = tone === 'emerald' ? 'from-emerald-400 to-green-300' : 'from-rose-500 to-red-400';

  return (
    <div className="w-11">
      <div className="h-1 overflow-hidden rounded-full border border-white/10 bg-black/55 shadow-[0_1px_4px_rgba(0,0,0,0.35)]">
        <div className={`h-full bg-gradient-to-r ${fill}`} style={{width: `${ratio * 100}%`}} />
      </div>
    </div>
  );
}

export function getPlayerWorldLeft(
  sideAnchor: string,
  playerX: number,
  cameraAnchorX: number,
) {
  return `calc(${sideAnchor} + ${(playerX - cameraAnchorX) * METERS_TO_PIXELS * 0.65}px)`;
}

export function getMonsterWorldLeft(
  sideAnchor: string,
  monsterX: number,
  cameraAnchorX: number,
  monsterAnchorMeters: number,
) {
  return `calc(100% - ${sideAnchor} + ${((monsterX - cameraAnchorX) - monsterAnchorMeters) * METERS_TO_PIXELS * 0.75}px - ${ENTITY_CONTAINER_REM}rem)`;
}

export function getCharacterOpponentBottom(
  groundBottom: string,
  stageLiftPx: number,
  character: Character | null | undefined,
) {
  const groundOffset = character?.groundOffsetY ?? 22;
  return `calc(${groundBottom} + ${stageLiftPx}px - ${groundOffset}px)`;
}

export function getNpcCombatHpTop(characterId?: string | null, monsterPresetId?: string | null) {
  if (monsterPresetId) return DEFAULT_COMBAT_HP_TOP_PX;
  return characterId ? CHARACTER_NPC_COMBAT_HP_TOP_PX : GENERIC_NPC_COMBAT_HP_TOP_PX;
}

export function getSceneEntityZIndex(bottomOffsetPx: number) {
  return Math.max(1, Math.min(9, 9 - Math.round(bottomOffsetPx / 16)));
}

export function getCharacterBottomOffsetPx(
  stageLiftPx: number,
  character: Character | null | undefined,
  extraOffsetPx = 0,
) {
  const groundOffset = character?.groundOffsetY ?? 22;
  return stageLiftPx - groundOffset + extraOffsetPx;
}

export function getEntityEffectBottom({
  origin,
  hostileNpcId,
  sceneCombatants,
  playerCharacter,
  groundBottom,
  stageLiftPx,
  playerOffsetY,
  anchorOffsetY = 0,
}: {
  origin: 'player' | 'hostile_npc' | 'monster';
  hostileNpcId?: string;
  sceneCombatants: SceneHostileNpc[];
  playerCharacter: Character | null;
  groundBottom: string;
  stageLiftPx: number;
  playerOffsetY: number;
  anchorOffsetY?: number;
}) {
  if (origin === 'player') {
    const playerGroundOffset = playerCharacter?.groundOffsetY ?? 22;
    return `calc(${groundBottom} + ${stageLiftPx}px - ${playerGroundOffset}px + ${playerOffsetY}px + ${anchorOffsetY}px)`;
  }

  const targetHostileNpc = hostileNpcId
    ? sceneCombatants.find(hostileNpc => hostileNpc.id === hostileNpcId)
    : null;

  if (!targetHostileNpc) {
    return `calc(${groundBottom} + ${stageLiftPx}px + ${anchorOffsetY}px)`;
  }

  if (targetHostileNpc.encounter?.characterId) {
    return getCharacterOpponentBottom(
      groundBottom,
      stageLiftPx + (targetHostileNpc.yOffset ?? 0) + anchorOffsetY,
      getCharacterById(targetHostileNpc.encounter.characterId),
    );
  }

  const genericNpcTargetOffset =
    targetHostileNpc.encounter
      && !targetHostileNpc.encounter.characterId
      && !targetHostileNpc.encounter.monsterPresetId
      ? GENERIC_NPC_EFFECT_TARGET_OFFSET_PX
      : 0;

  return `calc(${groundBottom} + ${stageLiftPx}px + ${((targetHostileNpc.yOffset ?? 0) + genericNpcTargetOffset + anchorOffsetY)}px)`;
}

export function RoleCharacterSprite({
  character,
  state,
  facing,
}: {
  character: Character;
  state: AnimationState;
  facing: 'left' | 'right';
}) {
  if (character.visual) {
    return (
      <MedievalNpcAnimator
        visualSpec={buildMedievalNpcVisualFromCustomWorldVisual(character.visual)}
        className="origin-bottom"
        scale={1.36}
        facing={facing}
      />
    );
  }

  return (
    <div className="h-full w-full" style={{transform: facing === 'left' ? 'scaleX(-1)' : undefined}}>
      <CharacterAnimator
        state={state}
        character={character}
        className={ROLE_CHARACTER_SPRITE_CLASS}
      />
    </div>
  );
}

export function DialogueBubbleIcon({
  active = false,
  flip = false,
}: {
  active?: boolean;
  flip?: boolean;
}) {
  const frameSequence = active ? CHAT_BUBBLE_ACTIVE_FRAMES : CHAT_BUBBLE_INACTIVE_FRAMES;
  const [frameCursor, setFrameCursor] = useState(0);

  useEffect(() => {
    setFrameCursor(0);

    const interval = window.setInterval(() => {
      setFrameCursor(prev => (prev + 1) % frameSequence.length);
    }, active ? 120 : 180);

    return () => window.clearInterval(interval);
  }, [active, frameSequence.length]);

  const frameIndex = frameSequence[frameCursor] ?? frameSequence[0] ?? 0;

  return (
    <div
      className="pointer-events-none"
      style={{
        width: `${CHAT_BUBBLE_FRAME_WIDTH}px`,
        height: `${CHAT_BUBBLE_FRAME_HEIGHT}px`,
        backgroundImage: `url("${CHAT_BUBBLE_SPRITE_SRC}")`,
        backgroundRepeat: 'no-repeat',
        backgroundPosition: `-${frameIndex * CHAT_BUBBLE_FRAME_WIDTH}px 0px`,
        backgroundSize: `${CHAT_BUBBLE_FRAME_WIDTH * CHAT_BUBBLE_FRAME_COUNT}px ${CHAT_BUBBLE_FRAME_HEIGHT}px`,
        imageRendering: 'pixelated',
        transform: `${flip ? 'scaleX(-1) ' : ''}scale(${active ? 1.15 : 1})`,
        transformOrigin: 'center',
        filter: active
          ? 'drop-shadow(0 0 8px rgba(251, 191, 36, 0.45))'
          : 'drop-shadow(0 2px 4px rgba(0, 0, 0, 0.45))',
      }}
    />
  );
}

export function SceneEntityButton({
  onClick,
  ariaLabel,
  className,
  style,
  children,
}: {
  onClick?: (() => void) | null;
  ariaLabel?: string;
  className?: string;
  style?: React.CSSProperties;
  children: React.ReactNode;
}) {
  if (!onClick) {
    return (
      <div className={className} style={style}>
        {children}
      </div>
    );
  }

  return (
    <button
      type="button"
      onClick={onClick}
      aria-label={ariaLabel}
      className={`group touch-manipulation transition-transform duration-150 hover:scale-[1.02] focus-visible:scale-[1.02] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-300/70 ${className ?? ''}`}
      style={style}
    >
      {children}
    </button>
  );
}
