import { useCallback, useEffect, useState, type CSSProperties } from 'react';
import {
  DAMAGE_NUMBER_DURATION_MS,
  DAMAGE_NUMBER_DRIFT_PX,
  DAMAGE_NUMBER_FEEDBACK_DURATION_MS,
  DAMAGE_NUMBER_CRIT_DURATION_MS,
  DAMAGE_NUMBER_RISE_PX,
} from '../shared/constants';
import type { FloatingDamageData } from '../game/types';

/** One-shot float + fade; not driven by rAF+setState so parent frame ticks cannot reset it */
const FLOAT_DAMAGE_KEYFRAMES = `
@keyframes autoheroFloatDamage {
  from {
    opacity: 1;
    transform: translate(-50%, -50%) translate(0, 0) scale(var(--ah-s0, 1));
  }
  to {
    opacity: 0;
    transform: translate(-50%, -50%) translate(var(--ah-drift, 0px), calc(-1 * var(--ah-rise, 0px))) scale(1);
  }
}
`;

interface FloatingDamageProps {
  damages: FloatingDamageData[];
}

interface DamageNumberProps {
  data: FloatingDamageData;
  onExpire: (id: number) => void;
}

function feedbackDurationMs(data: FloatingDamageData): number {
  if (data.kind === 'blocked' || data.kind === 'evaded') {
    return DAMAGE_NUMBER_FEEDBACK_DURATION_MS;
  }
  if (data.kind === 'damage' && Boolean(data.isCrit)) {
    return DAMAGE_NUMBER_CRIT_DURATION_MS;
  }
  return DAMAGE_NUMBER_DURATION_MS;
}

function DamageNumber({ data, onExpire }: DamageNumberProps) {
  const durationMs = feedbackDurationMs(data);
  const driftDir = data.target === 'enemy' ? 1 : -1;
  const isOutcomeText = data.kind === 'blocked' || data.kind === 'evaded';
  const isCritDamage = data.kind === 'damage' && Boolean(data.isCrit);
  const color = data.kind === 'regen'
    ? '#44dd66'
    : isOutcomeText
      ? (data.target === 'hero' ? '#44dd66' : '#ff5566')
      : (isCritDamage ? '#ffdd44' : '#ffffff');
  const fontSize = isOutcomeText ? 16 : (isCritDamage ? 24 : 18);

  const style: CSSProperties = {
    position: 'absolute',
    left: data.x,
    top: data.y,
    transform: 'translate(-50%, -50%)',
    animation: `autoheroFloatDamage ${durationMs}ms cubic-bezier(0.2, 0.75, 0.35, 1) forwards`,
    ['--ah-drift' as string]: `${DAMAGE_NUMBER_DRIFT_PX * driftDir}px`,
    ['--ah-rise' as string]: `${DAMAGE_NUMBER_RISE_PX}px`,
    ['--ah-s0' as string]: isCritDamage ? '1.4' : '1',
    color,
    fontSize,
    fontWeight: 900,
    textShadow: '0 2px 4px rgba(0,0,0,0.9), 0 0 8px rgba(0,0,0,0.5)',
    pointerEvents: 'none',
  };

  return (
    <div
      style={style}
      onAnimationEnd={() => {
        onExpire(data.id);
      }}
    >
      {data.kind === 'damage' && (
        <>
          {isCritDamage && 'CRIT '}
          {Math.round(data.value)}
        </>
      )}
      {data.kind === 'regen' && `+${Math.round(data.value)}`}
      {data.kind === 'blocked' && 'BLOCKED'}
      {data.kind === 'evaded' && 'EVADED'}
    </div>
  );
}

/**
 * Floating damage numbers overlay.
 * Renders above the game canvas in screen space.
 */
export function FloatingDamage({ damages }: FloatingDamageProps) {
  const [activeDamages, setActiveDamages] = useState<FloatingDamageData[]>([]);

  // Sync incoming damages
  useEffect(() => {
    setActiveDamages((prev) => {
      const existingIds = new Set(prev.map((d) => d.id));
      const newDamages = damages.filter((d) => !existingIds.has(d.id));
      return [...prev, ...newDamages];
    });
  }, [damages]);

  const handleExpire = useCallback((id: number) => {
    setActiveDamages((prev) => prev.filter((d) => d.id !== id));
  }, []);

  return (
    <div style={{
      position: 'absolute',
      top: 0,
      left: 0,
      width: '100%',
      height: '100%',
      pointerEvents: 'none',
      overflow: 'hidden',
    }}>
      <style dangerouslySetInnerHTML={{ __html: FLOAT_DAMAGE_KEYFRAMES }} />
      {activeDamages.map((d) => (
        <DamageNumber key={d.id} data={d} onExpire={handleExpire} />
      ))}
    </div>
  );
}
