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

import { useResolvedAssetReadUrl } from '../hooks/useResolvedAssetReadUrl';
import { AnimationState, Character, CharacterAnimationConfig } from '../types';

interface CharacterAnimatorProps {
  state: AnimationState;
  character: Character;
  className?: string;
  style?: React.CSSProperties;
  imageClassName?: string;
  playbackRate?: number;
}

const DEFAULT_ANIMATIONS: Record<AnimationState, CharacterAnimationConfig> = {
  [AnimationState.ACQUIRE]: { frames: 1, prefix: 'acquire', folder: 'acquire' },
  [AnimationState.ATTACK]: { frames: 1, prefix: 'Attack', folder: 'attack' },
  [AnimationState.RUN]: { frames: 1, prefix: 'Run', folder: 'run' },
  [AnimationState.DOUBLE_JUMP]: {
    frames: 1,
    prefix: 'double jump',
    folder: 'double jump',
  },
  [AnimationState.JUMP_ATTACK]: {
    frames: 1,
    prefix: 'jump attack',
    folder: 'jump attack',
  },
  [AnimationState.DASH]: { frames: 1, prefix: 'dash', folder: 'dash' },
  [AnimationState.HURT]: { frames: 1, prefix: 'hurt', folder: 'hurt' },
  [AnimationState.DIE]: { frames: 1, prefix: 'die', folder: 'die' },
  [AnimationState.CLIMB]: { frames: 1, prefix: 'Climb', folder: 'climb' },
  [AnimationState.SKILL1]: { frames: 1, prefix: 'skill1', folder: 'skill1' },
  [AnimationState.SKILL1_JUMP]: {
    frames: 1,
    prefix: 'skill1 jump',
    folder: 'skill1 jump',
  },
  [AnimationState.SKILL1_BULLET]: {
    frames: 1,
    prefix: 'skill1 bullet',
    folder: 'skill1 bullet',
  },
  [AnimationState.SKILL1_BULLET_FX]: {
    frames: 1,
    prefix: 'skill1 bullet FX',
    folder: 'skill1 bullet FX',
  },
  [AnimationState.SKILL2]: { frames: 1, prefix: 'skill2', folder: 'skill2' },
  [AnimationState.SKILL2_JUMP]: {
    frames: 1,
    prefix: 'skill2 jump',
    folder: 'skill2 jump',
  },
  [AnimationState.SKILL3]: { frames: 1, prefix: 'skill3', folder: 'skill3' },
  [AnimationState.SKILL3_JUMP]: {
    frames: 1,
    prefix: 'skill3 jump',
    folder: 'skill3 jump',
  },
  [AnimationState.SKILL3_BULLET]: {
    frames: 1,
    prefix: 'skill3 bullet',
    folder: 'skill3 bullet',
  },
  [AnimationState.SKILL3_BULLET_FX]: {
    frames: 1,
    prefix: 'skill3 bullet FX',
    folder: 'skill3 bullet FX',
  },
  [AnimationState.SKILL4]: { frames: 1, prefix: 'skill4', folder: 'skill4' },
  [AnimationState.WALL_SLIDE]: {
    frames: 1,
    prefix: 'Wall Slide',
    folder: 'Wall Slide',
  },
  [AnimationState.IDLE]: { frames: 1, prefix: 'Idle', folder: 'idle' },
  [AnimationState.JUMP]: { frames: 1, prefix: 'Jump', folder: 'jump' },
};

const PORTRAIT_FALLBACK_ANIMATION: CharacterAnimationConfig = {
  frames: 1,
  prefix: 'portrait',
  folder: 'portrait',
  fps: 1,
  loop: false,
};

const FALLEN_PORTRAIT_STYLE: React.CSSProperties = {
  imageRendering: 'pixelated',
  transform: 'translateY(16%) rotate(-90deg) scaleX(-1) scale(0.82)',
  transformOrigin: '50% 85%',
  animation:
    'character-animator-portrait-death-fall 680ms cubic-bezier(0.22, 0.7, 0.18, 1) forwards',
};

const DEFAULT_IMAGE_STYLE: React.CSSProperties = {
  imageRendering: 'pixelated',
};

export const CharacterAnimator: React.FC<CharacterAnimatorProps> = ({
  state,
  character,
  className,
  style,
  imageClassName,
  playbackRate = 1,
}) => {
  const explicitConfig = character.animationMap?.[state];
  const usePortraitIdleFallback =
    !explicitConfig && state === AnimationState.IDLE;
  const usePortraitDeathFallback =
    !explicitConfig && state === AnimationState.DIE;
  const [hasRenderError, setHasRenderError] = useState(false);
  const baseConfig =
    explicitConfig ??
    DEFAULT_ANIMATIONS[state] ??
    character.animationMap?.[AnimationState.IDLE] ??
    DEFAULT_ANIMATIONS[AnimationState.IDLE];
  const fallbackToPortrait =
    usePortraitIdleFallback || usePortraitDeathFallback || hasRenderError;
  const config = fallbackToPortrait ? PORTRAIT_FALLBACK_ANIMATION : baseConfig;
  const startFrame =
    typeof config.startFrame === 'number' && Number.isFinite(config.startFrame)
      ? Math.max(1, Math.floor(config.startFrame))
      : 1;
  const [frameIndex, setFrameIndex] = useState(startFrame);
  const frameCount =
    typeof config.frames === 'number' && Number.isFinite(config.frames)
      ? Math.max(1, Math.floor(config.frames))
      : 1;
  const fps =
    typeof config.fps === 'number' && Number.isFinite(config.fps)
      ? Math.max(1, config.fps)
      : 10;
  const effectivePlaybackRate = Number.isFinite(playbackRate)
    ? Math.max(0.1, playbackRate)
    : 1;
  const requestedAnimationSignature = [
    state,
    character.id,
    character.portrait,
    baseConfig.basePath ?? '',
    baseConfig.folder,
    baseConfig.prefix,
    baseConfig.file ?? '',
    baseConfig.extension ?? 'png',
    baseConfig.startFrame ?? 1,
    baseConfig.frames,
    baseConfig.fps ?? 10,
    effectivePlaybackRate,
  ].join('::');
  const animationSignature = [
    state,
    config.basePath ?? '',
    config.folder,
    config.prefix,
    config.file ?? '',
    config.extension ?? 'png',
    startFrame,
    frameCount,
    fps,
    effectivePlaybackRate,
  ].join('::');

  useEffect(() => {
    setHasRenderError(false);
  }, [requestedAnimationSignature]);

  const endFrame = startFrame + frameCount - 1;
  const intervalDelay = Math.max(
    40,
    Math.round(1000 / (fps * effectivePlaybackRate)),
  );

  useEffect(() => {
    setFrameIndex((current) => (current === startFrame ? current : startFrame));
  }, [animationSignature, startFrame]);

  useEffect(() => {
    if (frameCount <= 1) return;

    const interval = window.setInterval(() => {
      setFrameIndex((current) => {
        if (current < startFrame || current > endFrame) {
          return startFrame;
        }
        return current >= endFrame ? startFrame : current + 1;
      });
    }, intervalDelay);

    return () => window.clearInterval(interval);
  }, [endFrame, frameCount, intervalDelay, startFrame]);

  const frameNumber = frameIndex.toString().padStart(2, '0');
  const normalizedBasePath = config.basePath?.replace(/\/+$/u, '');
  const generatedImagePath = normalizedBasePath
    ? config.file
      ? `${normalizedBasePath}/${encodeURIComponent(config.file)}`
      : `${normalizedBasePath}/${config.prefix}${frameNumber}.${config.extension ?? 'png'}`
    : (() => {
        const folder = encodeURIComponent(character.assetFolder);
        const variant = encodeURIComponent(character.assetVariant);
        const animationFolder = encodeURIComponent(config.folder);
        return config.file
          ? `/character/${folder}/${variant}/Hero/${animationFolder}/${encodeURIComponent(config.file)}`
          : `/character/${folder}/${variant}/Hero/${animationFolder}/${config.prefix}${frameNumber}.${config.extension ?? 'png'}`;
      })();
  const imagePath = fallbackToPortrait
    ? character.portrait
    : generatedImagePath;
  const {
    resolvedUrl: resolvedImagePath,
    shouldResolve: shouldResolveImagePath,
  } = useResolvedAssetReadUrl(imagePath);
  // 私有 OSS 资源必须等签名地址返回后再渲染，不能先落回原始 generated-* 路径。
  const displayImagePath =
    resolvedImagePath || (!shouldResolveImagePath ? imagePath : '');
  const resolvedImageClassName =
    `h-full w-full object-contain pixelated ${imageClassName ?? ''}`.trim();
  const imageStyle =
    state === AnimationState.DIE && (usePortraitDeathFallback || hasRenderError)
      ? FALLEN_PORTRAIT_STYLE
      : DEFAULT_IMAGE_STYLE;

  if (!displayImagePath) {
    return <div className={`relative ${className ?? ''}`} style={style} />;
  }

  return (
    <div className={`relative ${className ?? ''}`} style={style}>
      <img
        src={displayImagePath}
        alt={`${character.name} ${state} animation`}
        className={resolvedImageClassName}
        style={imageStyle}
        onError={(e) => {
          if (!hasRenderError) {
            setHasRenderError(true);
          }
        }}
      />
    </div>
  );
};
