import { useCallback, useEffect, useRef, useState } from "react";
import { SPEEDS } from "@/lib/constants";
import type { Meta } from "@/types/trajectory";

/** Format current time from step when time_start/time_end exist (calendar display). When dt_hours === 1, one step = one hour (hora punta). */
function getTimeFromStep(
  step: number,
  maxStep: number,
  timeStart: string,
  timeEnd: string,
  dtHours?: number
): { day: number; hour: number; dateLabel: string; startLabel: string; endLabel: string } {
  const start = new Date(timeStart).getTime();
  const end = new Date(timeEnd).getTime();
  const t =
    maxStep <= 0
      ? start
      : dtHours === 1
        ? start + step * 3600 * 1000
        : start + (step / maxStep) * (end - start);
  const d = new Date(t);
  const hoursSinceStart = (t - start) / (1000 * 3600);
  const day = Math.floor(hoursSinceStart / 24) + 1;
  const hour = Math.floor(hoursSinceStart % 24);
  const fmt = (date: Date) =>
    date.toLocaleDateString("en-GB", { day: "numeric", month: "short", hour: "2-digit", minute: "2-digit", hour12: false });
  return {
    day,
    hour,
    dateLabel: fmt(d),
    startLabel: fmt(new Date(start)),
    endLabel: fmt(new Date(end)),
  };
}

export function useSimulation(meta: Meta | null): {
  step: number;
  setStep: (value: number | ((prev: number) => number)) => void;
  playing: boolean;
  speedIdx: number;
  togglePlay: () => void;
  stepBack: () => void;
  stepForward: () => void;
  restart: () => void;
  cycleSpeed: () => void;
  onSlider: (value: number[]) => void;
  maxStep: number;
  day: number;
  hour: number;
  dateLabel: string | null;
  startLabel: string | null;
  endLabel: string | null;
} {
  const [step, setStep] = useState(0);
  const [playing, setPlaying] = useState(false);
  const [speedIdx, setSpeedIdx] = useState(1);
  const rafRef = useRef<number | null>(null);
  const lastStepTimeRef = useRef<number>(0);
  const stepIntervalRef = useRef(500 / SPEEDS[1]);

  const maxStep = meta ? meta.num_steps - 1 : 0;
  const hasCalendar =
    meta?.time_start && meta?.time_end && maxStep > 0;
  const { day, hour, dateLabel, startLabel, endLabel } = hasCalendar
    ? getTimeFromStep(step, maxStep, meta.time_start!, meta.time_end!, meta.dt_hours)
    : {
        day: Math.floor(step / 24) + 1,
        hour: step % 24,
        dateLabel: null as string | null,
        startLabel: null as string | null,
        endLabel: null as string | null,
      };

  const togglePlay = useCallback(() => {
    setPlaying((p) => {
      if (!p && meta && step >= meta.num_steps - 1) setStep(0);
      return !p;
    });
  }, [step, meta]);

  const cycleSpeed = useCallback(() => {
    setSpeedIdx((i) => (i + 1) % SPEEDS.length);
  }, []);

  const stepBack = useCallback(() => {
    setStep((s) => Math.max(0, s - 1));
  }, []);

  const stepForward = useCallback(() => {
    setStep((s) => (meta ? Math.min(meta.num_steps - 1, s + 1) : s));
  }, [meta]);

  const restart = useCallback(() => {
    setStep(0);
    setPlaying(false);
  }, []);

  const onSlider = useCallback(
    (value: number[]) => {
      const raw = value[0] ?? 0;
      const clamped = Math.max(0, Math.min(maxStep, Math.round(raw)));
      setStep(clamped);
    },
    [maxStep]
  );

  useEffect(() => {
    if (!playing || !meta) return;
    const dtHours = meta.dt_hours;
    const msPerStep = dtHours === 1 ? 1000 / SPEEDS[speedIdx] : 500 / SPEEDS[speedIdx];
    stepIntervalRef.current = msPerStep;
    lastStepTimeRef.current = performance.now();

    const tick = (now: number) => {
      const elapsed = now - lastStepTimeRef.current;
      if (elapsed >= stepIntervalRef.current) {
        lastStepTimeRef.current = now;
        setStep((prev) => {
          if (prev >= meta.num_steps - 1) {
            setPlaying(false);
            return prev;
          }
          return prev + 1;
        });
      }
      rafRef.current = requestAnimationFrame(tick);
    };
    rafRef.current = requestAnimationFrame(tick);
    return () => {
      if (rafRef.current != null) cancelAnimationFrame(rafRef.current);
    };
  }, [playing, meta, speedIdx]);

  useEffect(() => {
    const onKeyDown = (e: KeyboardEvent) => {
      if (
        e.target instanceof HTMLInputElement ||
        e.target instanceof HTMLTextAreaElement
      )
        return;
      switch (e.key) {
        case " ":
          e.preventDefault();
          togglePlay();
          break;
        case "ArrowLeft":
          e.preventDefault();
          stepBack();
          break;
        case "ArrowRight":
          e.preventDefault();
          stepForward();
          break;
        default:
          break;
      }
    };
    window.addEventListener("keydown", onKeyDown);
    return () => window.removeEventListener("keydown", onKeyDown);
  }, [togglePlay, stepBack, stepForward]);

  return {
    step,
    setStep,
    playing,
    speedIdx,
    togglePlay,
    stepBack,
    stepForward,
    restart,
    cycleSpeed,
    onSlider,
    maxStep,
    day,
    hour,
    dateLabel,
    startLabel,
    endLabel,
  };
}
