import { ref, computed, type Ref, onUnmounted, toRef, isRef } from 'vue';
import type { Sprite } from '@/types/sprites';

export interface AnimationFramesOptions {
  sprites: Ref<Sprite[]> | Sprite[] | (() => Sprite[]);
  onDraw: () => void;
}

export function useAnimationFrames(options: AnimationFramesOptions) {
  const { onDraw } = options;

  // Convert sprites to a computed ref for reactivity
  const spritesRef = computed(() => {
    if (typeof options.sprites === 'function') {
      return options.sprites();
    }
    if (isRef(options.sprites)) {
      return options.sprites.value;
    }
    return options.sprites;
  });

  // Helper to get sprites array
  const getSprites = () => spritesRef.value;

  // State
  const currentFrameIndex = ref(0);
  const isPlaying = ref(false);
  const fps = ref(12);
  const hiddenFrames = ref<number[]>([]);

  // Animation internals
  const animationFrameId = ref<number | null>(null);
  const lastFrameTime = ref(0);

  // Computed properties for visible frames
  const visibleFrames = computed(() => getSprites().filter((_, index) => !hiddenFrames.value.includes(index)));

  const visibleFramesCount = computed(() => visibleFrames.value.length);

  const visibleFrameIndex = computed(() => {
    const sprites = getSprites();
    const currentSprite = sprites[currentFrameIndex.value];
    return visibleFrames.value.findIndex(s => s === currentSprite);
  });

  const visibleFrameNumber = computed(() => visibleFrameIndex.value + 1);

  // Animation control
  const animateFrame = () => {
    const now = performance.now();
    const elapsed = now - lastFrameTime.value;
    const frameTime = 1000 / fps.value;

    if (elapsed >= frameTime) {
      lastFrameTime.value = now - (elapsed % frameTime);
      nextFrame();
    }

    animationFrameId.value = requestAnimationFrame(animateFrame);
  };

  const startAnimation = () => {
    lastFrameTime.value = performance.now();
    animateFrame();
  };

  const stopAnimation = () => {
    if (animationFrameId.value !== null) {
      cancelAnimationFrame(animationFrameId.value);
      animationFrameId.value = null;
    }
  };

  const togglePlayback = () => {
    isPlaying.value = !isPlaying.value;

    if (isPlaying.value) {
      startAnimation();
    } else {
      stopAnimation();
    }
  };

  const nextFrame = () => {
    if (visibleFrames.value.length === 0) return;
    const sprites = getSprites();

    const currentVisibleIndex = visibleFrameIndex.value;
    const nextVisibleIndex = (currentVisibleIndex + 1) % visibleFrames.value.length;
    currentFrameIndex.value = sprites.indexOf(visibleFrames.value[nextVisibleIndex]);
    onDraw();
  };

  const previousFrame = () => {
    if (visibleFrames.value.length === 0) return;
    const sprites = getSprites();

    const currentVisibleIndex = visibleFrameIndex.value;
    const prevVisibleIndex = (currentVisibleIndex - 1 + visibleFrames.value.length) % visibleFrames.value.length;
    currentFrameIndex.value = sprites.indexOf(visibleFrames.value[prevVisibleIndex]);
    onDraw();
  };

  const handleSliderInput = (event: Event) => {
    const sprites = getSprites();
    const target = event.target as HTMLInputElement;
    const index = parseInt(target.value);
    currentFrameIndex.value = sprites.indexOf(visibleFrames.value[index]);
  };

  // Frame visibility management
  const toggleHiddenFrame = (index: number) => {
    const sprites = getSprites();
    const currentIndex = hiddenFrames.value.indexOf(index);

    if (currentIndex === -1) {
      hiddenFrames.value.push(index);

      // If hiding current frame, switch to next visible
      if (index === currentFrameIndex.value) {
        const nextVisible = sprites.findIndex((_, i) => i !== index && !hiddenFrames.value.includes(i));
        if (nextVisible !== -1) {
          currentFrameIndex.value = nextVisible;
        }
      }
    } else {
      hiddenFrames.value.splice(currentIndex, 1);
    }

    onDraw();
  };

  const showAllFrames = () => {
    hiddenFrames.value = [];
    onDraw();
  };

  const hideAllFrames = () => {
    const sprites = getSprites();
    hiddenFrames.value = sprites.map((_, index) => index);

    // Keep at least one frame visible
    if (hiddenFrames.value.length > 0) {
      hiddenFrames.value.splice(currentFrameIndex.value, 1);
    }
    onDraw();
  };

  // Cleanup on unmount
  onUnmounted(() => {
    stopAnimation();
  });

  return {
    // State
    currentFrameIndex,
    isPlaying,
    fps,
    hiddenFrames,

    // Computed
    visibleFrames,
    visibleFramesCount,
    visibleFrameIndex,
    visibleFrameNumber,

    // Methods
    togglePlayback,
    nextFrame,
    previousFrame,
    handleSliderInput,
    toggleHiddenFrame,
    showAllFrames,
    hideAllFrames,
    stopAnimation,
  };
}
