import { useEffect, useRef, useState } from "react";

function valuesMatch(left, right) {
  return JSON.stringify(left) === JSON.stringify(right);
}

export function useThrottledParameterValue(parameter, onParameterChange) {
  const currentValue = parameter.value === undefined ? parameter.defaultValue : parameter.value;
  const [draftValue, setDraftValue] = useState(currentValue);
  const [appliedValue, setAppliedValue] = useState(currentValue);
  const pendingTimeoutRef = useRef(null);
  const latestDraftRef = useRef(currentValue);
  const lastSentAtRef = useRef(0);
  const isInteractingRef = useRef(false);
  const isDirtyRef = useRef(false);

  useEffect(() => {
    setDraftValue(currentValue);
    setAppliedValue(currentValue);
    latestDraftRef.current = currentValue;
    lastSentAtRef.current = 0;
    isInteractingRef.current = false;
    isDirtyRef.current = false;
  }, [parameter.id]);

  useEffect(() => {
    setAppliedValue(currentValue);
    latestDraftRef.current = draftValue;
    if (isDirtyRef.current && valuesMatch(currentValue, latestDraftRef.current)) {
      isDirtyRef.current = false;
    }
    if (!isInteractingRef.current && !isDirtyRef.current) {
      setDraftValue(currentValue);
    }
  }, [draftValue, currentValue]);

  useEffect(() => {
    if (isInteractingRef.current) {
      return;
    }

    if (valuesMatch(currentValue, latestDraftRef.current)) {
      return;
    }

    if (pendingTimeoutRef.current) {
      clearTimeout(pendingTimeoutRef.current);
      pendingTimeoutRef.current = null;
    }

    setDraftValue(currentValue);
    setAppliedValue(currentValue);
    latestDraftRef.current = currentValue;
    isDirtyRef.current = false;
    lastSentAtRef.current = 0;
  }, [currentValue]);

  useEffect(() => {
    return () => {
      if (pendingTimeoutRef.current) {
        clearTimeout(pendingTimeoutRef.current);
      }
    };
  }, []);

  const sendValue = (value) => {
    setDraftValue(value);
    latestDraftRef.current = value;
    isDirtyRef.current = true;
    lastSentAtRef.current = Date.now();
    const request = onParameterChange(parameter.id, value);
    if (request && typeof request.then === "function") {
      request
        .then((response) => {
          if (response?.ok) {
            setAppliedValue(value);
          }
        })
        .catch(() => {
          // Keep showing the last confirmed value if the update failed.
        });
    }
  };

  const scheduleSendValue = (value, immediate = false, throttleMs = 90) => {
    setDraftValue(value);
    latestDraftRef.current = value;
    isDirtyRef.current = true;

    if (pendingTimeoutRef.current) {
      clearTimeout(pendingTimeoutRef.current);
      pendingTimeoutRef.current = null;
    }

    const now = Date.now();
    const elapsed = now - lastSentAtRef.current;

    if (immediate || elapsed >= throttleMs) {
      sendValue(value);
      return;
    }

    pendingTimeoutRef.current = setTimeout(() => {
      pendingTimeoutRef.current = null;
      sendValue(latestDraftRef.current);
    }, throttleMs - elapsed);
  };

  const flushScheduledValue = () => {
    if (pendingTimeoutRef.current) {
      clearTimeout(pendingTimeoutRef.current);
      pendingTimeoutRef.current = null;
    }
    sendValue(latestDraftRef.current);
  };

  const beginInteraction = () => {
    isInteractingRef.current = true;
  };

  const endInteraction = () => {
    isInteractingRef.current = false;
    flushScheduledValue();
  };

  return {
    appliedValue,
    beginInteraction,
    draftValue,
    endInteraction,
    isPending: !valuesMatch(draftValue, appliedValue),
    scheduleSendValue,
    sendValue,
  };
}
