import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
  computeNextIndex,
  computePrevIndex,
  computeUserPromptIndices,
  findBatchEnd,
  findNextUserPrompt,
  findPrevUserPrompt,
  sceneDuration,
} from "../engine";
import type { Scene } from "../types";
import type { EffectivePrefs } from "./useViewPrefs";

export type PlayState = "idle" | "playing" | "paused" | "ended";

function isEditableTarget(target: EventTarget | null): boolean {
  const el = target as HTMLElement | null;
  if (!el) return false;
  const tag = el.tagName;
  if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
  if (el.isContentEditable) return true;
  const role = el.getAttribute("role");
  return role === "textbox" || role === "combobox";
}

export function usePlayback(scenes: Scene[], prefs: EffectivePrefs, enabled = true) {
  const [state, setState] = useState<PlayState>("idle");
  const [currentIndex, setCurrentIndex] = useState(-1);
  const [speed, setSpeed] = useState(1);
  const [visibleCount, setVisibleCount] = useState(0);

  const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const stateRef = useRef(state);
  const indexRef = useRef(currentIndex);
  const speedRef = useRef(speed);
  const prefsRef = useRef(prefs);

  stateRef.current = state;
  indexRef.current = currentIndex;
  speedRef.current = speed;
  prefsRef.current = prefs;

  // Compute user prompt indices for jump navigation
  const userPromptIndices = useMemo(() => computeUserPromptIndices(scenes), [scenes]);

  const clearTimer = useCallback(() => {
    if (timerRef.current) {
      clearTimeout(timerRef.current);
      timerRef.current = null;
    }
  }, []);

  const advanceScene = useCallback(() => {
    if (stateRef.current !== "playing") return;

    const nextIdx = computeNextIndex(scenes, indexRef.current, prefsRef.current);
    if (nextIdx === -1) {
      setState("ended");
      setCurrentIndex(scenes.length - 1);
      setVisibleCount(scenes.length);
      return;
    }

    setCurrentIndex(nextIdx);
    setVisibleCount(nextIdx + 1);
    const duration = sceneDuration(scenes[nextIdx], speedRef.current);
    timerRef.current = setTimeout(advanceScene, duration);
  }, [scenes]);

  const play = useCallback(() => {
    if (scenes.length === 0) return;

    clearTimer();

    if (stateRef.current === "ended") {
      setCurrentIndex(-1);
      setVisibleCount(0);
    }

    setState("playing");
    // Store bootstrap timer in ref so rapid play() calls cannot stack multiple bootstraps
    timerRef.current = setTimeout(() => {
      // Bail out if no longer playing (e.g. user paused during the 50ms delay)
      if (stateRef.current !== "playing") return;

      if (indexRef.current < 0) {
        // Starting fresh — check if first scene starts a batch
        const endIdx = findBatchEnd(scenes, 0);
        setCurrentIndex(endIdx);
        setVisibleCount(endIdx + 1);
        const duration = sceneDuration(scenes[endIdx], speedRef.current);
        timerRef.current = setTimeout(advanceScene, duration);
      } else {
        const currentScene = scenes[indexRef.current];
        if (currentScene) {
          const duration = sceneDuration(currentScene, speedRef.current) / 2;
          timerRef.current = setTimeout(advanceScene, duration);
        }
      }
    }, 50);
  }, [scenes, advanceScene, clearTimer]);

  const pause = useCallback(() => {
    clearTimer();
    setState("paused");
  }, [clearTimer]);

  const togglePlayPause = useCallback(() => {
    if (stateRef.current === "playing") {
      pause();
    } else {
      play();
    }
  }, [play, pause]);

  const seekTo = useCallback(
    (index: number) => {
      clearTimer();
      const clamped = Math.max(0, Math.min(index, scenes.length - 1));
      setCurrentIndex(clamped);
      setVisibleCount(clamped + 1);

      if (stateRef.current === "playing") {
        const duration = sceneDuration(scenes[clamped], speedRef.current);
        timerRef.current = setTimeout(advanceScene, duration);
      }

      if (stateRef.current === "ended" || stateRef.current === "idle") {
        setState("paused");
      }
    },
    [scenes, clearTimer, advanceScene],
  );

  const jumpToNextUserPrompt = useCallback(() => {
    const next = findNextUserPrompt(userPromptIndices, indexRef.current);
    if (next !== undefined) seekTo(next);
  }, [userPromptIndices, seekTo]);

  const jumpToPrevUserPrompt = useCallback(() => {
    const prev = findPrevUserPrompt(userPromptIndices, indexRef.current);
    if (prev !== undefined) seekTo(prev);
  }, [userPromptIndices, seekTo]);

  const changeSpeed = useCallback(
    (newSpeed: number) => {
      setSpeed(newSpeed);
      if (stateRef.current === "playing") {
        clearTimer();
        const currentScene = scenes[indexRef.current];
        if (currentScene) {
          const duration = sceneDuration(currentScene, newSpeed);
          timerRef.current = setTimeout(advanceScene, duration);
        }
      }
    },
    [scenes, clearTimer, advanceScene],
  );

  useEffect(() => clearTimer, [clearTimer]);

  // Keyboard shortcuts — only when enabled (after landing page dismissed)
  useEffect(() => {
    if (!enabled) return;
    const handler = (e: KeyboardEvent) => {
      // Do not hijack keys while user is typing in search/input controls.
      if (isEditableTarget(e.target)) return;
      // Space — play/pause
      if (e.key === " ") {
        e.preventDefault();
        togglePlayPause();
      }
      // Arrow Down / j — next scene
      else if (e.key === "ArrowDown" || e.key === "j") {
        if (stateRef.current === "playing") pause();
        e.preventDefault();
        const nextIdx = computeNextIndex(scenes, indexRef.current, prefsRef.current);
        if (nextIdx !== -1) seekTo(nextIdx);
      }
      // Arrow Up / k — prev scene (vim convention)
      else if (e.key === "ArrowUp" || e.key === "k") {
        if (stateRef.current === "playing") pause();
        e.preventDefault();
        const prevIdx = computePrevIndex(scenes, indexRef.current, prefsRef.current);
        seekTo(prevIdx);
      }
      // Arrow Left / h — collapse
      else if (e.key === "ArrowLeft" || e.key === "h") {
        e.preventDefault();
        window.dispatchEvent(
          new CustomEvent("vibe:toggle-expand", { detail: { action: "collapse" } }),
        );
      }
      // Escape — pause (consistent with Claude Code)
      else if (e.key === "Escape") {
        if (stateRef.current === "playing") {
          e.preventDefault();
          pause();
        }
      }
      // Arrow Right / l — expand
      else if (e.key === "ArrowRight" || e.key === "l") {
        e.preventDefault();
        window.dispatchEvent(
          new CustomEvent("vibe:toggle-expand", { detail: { action: "expand" } }),
        );
      }
      // n — next user prompt
      else if (e.key === "n") {
        e.preventDefault();
        jumpToNextUserPrompt();
      }
      // p — prev user prompt
      else if (e.key === "p") {
        e.preventDefault();
        jumpToPrevUserPrompt();
      }
      // Home — start
      else if (e.key === "Home") {
        e.preventDefault();
        seekTo(0);
      }
      // End / e — show all
      else if (e.key === "End" || e.key === "e") {
        e.preventDefault();
        seekTo(scenes.length - 1);
      }
    };
    window.addEventListener("keydown", handler);
    return () => window.removeEventListener("keydown", handler);
  }, [enabled, togglePlayPause, seekTo, jumpToNextUserPrompt, jumpToPrevUserPrompt, scenes, pause]);

  return {
    state,
    currentIndex,
    visibleCount,
    speed,
    play,
    pause,
    togglePlayPause,
    seekTo,
    changeSpeed,
    jumpToNextUserPrompt,
    jumpToPrevUserPrompt,
    userPromptIndices,
    totalScenes: scenes.length,
    computeNextIndex: useCallback(
      (idx: number) => computeNextIndex(scenes, idx, prefs),
      [scenes, prefs],
    ),
    computePrevIndex: useCallback(
      (idx: number) => computePrevIndex(scenes, idx, prefs),
      [scenes, prefs],
    ),
  };
}
