import { useEffect, useMemo, useRef, useState } from 'react';
import { useActivePet } from '../../hooks/useActivePet';
import { useNodeAnimationState } from '../../hooks/useNodeAnimationState';
import { usePetFrameAnalysis } from '../../hooks/usePetFrameAnalysis';
import { extractLatestToolName, petStateFromJobStatus, type PetRuntimeState } from '../../lib/pets/petState';
import { PetSprite } from './PetSprite';
import { ToolSpark } from './ToolSpark';

const JUMP_DURATION_MS = 1500;
const DONE_FLOURISH_MS = 1000;

function usePrefersReducedMotion(): boolean {
  const [reduced, setReduced] = useState(false);

  useEffect(() => {
    const query = window.matchMedia('(prefers-reduced-motion: reduce)');
    const update = () => setReduced(query.matches);
    update();
    query.addEventListener('change', update);
    return () => query.removeEventListener('change', update);
  }, []);

  return reduced;
}

export function ChatPetOverlay({
  taskId,
  taskStatus,
  currentActivity,
  workerId,
  lastBackendId,
  className,
}: {
  taskId: number | null;
  taskStatus: string | null;
  currentActivity: string | null;
  workerId: string | null;
  /**
   * Physical backend id when the worker is a proxy (LiteLLM deployment
   * name from `x-litellm-model-id`). Falls through to workerId mapping
   * if unset or unmapped. Phase A: passed in from latestJob.lastBackendId.
   */
  lastBackendId?: string | null;
  /** Extra classes appended to the overlay wrapper. Used to gate
   * visibility per breakpoint when multiple instances render (e.g.,
   * one inside ChatPane for tablet+, another at app level for mobile). */
  className?: string;
}) {
  const { data, isLoading } = useActivePet(workerId, lastBackendId);
  const framesPerRow = usePetFrameAnalysis(
    data?.spriteUrl ?? null,
    data?.gridCols ?? null,
    data?.gridRows ?? null,
  );
  const prefersReducedMotion = usePrefersReducedMotion();

  // Phase C: when the job hasn't reported `running` yet (e.g. fresh
  // queued state, or status SSE hasn't caught up) but the backend
  // node is actually busy on our work, surface the running animation
  // anyway. Prefer the proxy-backend mapping over the worker mapping
  // — same precedence as useActivePet uses for sprite selection.
  const nodeAnimState = useNodeAnimationState(lastBackendId ?? workerId ?? null);

  const taskBaseState = petStateFromJobStatus(taskStatus, taskId);
  // Promote an 'idle' base state to 'running' when the backing node is
  // actively processing. Don't override informative states like
  // 'dispatching', 'waiting', 'done', or 'error' — those carry signal
  // that node.busy doesn't.
  const baseState: PetRuntimeState = taskBaseState === 'idle' && nodeAnimState === 'running'
    ? 'running'
    : taskBaseState;
  const baseStateRef = useRef(baseState);
  baseStateRef.current = baseState;

  const [displayState, setDisplayState] = useState<PetRuntimeState>('idle');

  // Reset display to base state whenever it changes; brief 'done' flourish then
  // settle to idle so the wave doesn't loop forever.
  useEffect(() => {
    setDisplayState(baseState);
    if (baseState !== 'done') return;
    const timer = window.setTimeout(() => setDisplayState('idle'), DONE_FLOURISH_MS);
    return () => window.clearTimeout(timer);
  }, [baseState]);

  // When the current activity changes (= a new tool fired) during active
  // execution, jump for ~1.5s and revert to whatever the base state is by then.
  useEffect(() => {
    if (!currentActivity) return;
    const active = baseStateRef.current;
    if (active !== 'running' && active !== 'runningAlt' && active !== 'dispatching') return;
    setDisplayState('jumping');
    const timer = window.setTimeout(() => {
      const current = baseStateRef.current;
      if (current === 'running' || current === 'runningAlt' || current === 'dispatching') {
        setDisplayState(current);
      }
      // For other base states (done / error / idle / waiting) the dedicated
      // effects above will have taken over; don't fight them here.
    }, JUMP_DURATION_MS);
    return () => window.clearTimeout(timer);
  }, [currentActivity]);

  const toolName = useMemo(
    () => extractLatestToolName(currentActivity),
    [currentActivity],
  );

  if (isLoading || !data?.settings.enabled || !data.pet) return null;

  const reducedMotion = data.settings.reducedMotion || prefersReducedMotion;

  return (
    <div
      className={className ? `chat-pet-overlay ${className}` : 'chat-pet-overlay'}
      style={{ ['--pet-size' as string]: `${data.settings.size}px` }}
      aria-hidden="true"
    >
      <ToolSpark
        toolName={toolName}
        activityKey={currentActivity}
        enabled={data.settings.toolSparkEnabled}
        reducedMotion={reducedMotion}
      />
      <PetSprite
        name={data.pet.name}
        imageUrl={data.imageUrl}
        frameWidth={data.frameWidth}
        frameHeight={data.frameHeight}
        gridCols={data.gridCols}
        gridRows={data.gridRows}
        framesPerRow={framesPerRow}
        state={displayState}
        size={data.settings.size}
        reducedMotion={reducedMotion}
      />
    </div>
  );
}
