import React, { useCallback, useEffect, useMemo, useRef } from "react";
import styled from "styled-components";

import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faArrowLeft, faArrowRight } from "@fortawesome/free-solid-svg-icons";

import ECG from "./ECG";

const NavigatorRoot = styled.div`
  position: relative;
  width: 100%;
`;

const ViewportTrack = styled.div`
  position: absolute;
  inset: 0;
  z-index: 2;
  cursor: pointer;
  touch-action: none;
`;

const ViewportWindow = styled.div.attrs(({ $left, $width }) => ({
  style: {
    left: `${$left}px`,
    width: `${$width}px`,
  },
}))`
  box-sizing: border-box;
  height: 100%;
  position: absolute;
  top: 0;
  z-index: 3;
  border: 1px solid rgba(0, 79, 236, 0.24);
  border-radius: 12px;
  background: rgba(88, 0, 255, 0.12);
  cursor: ${({ $dragging }) => ($dragging ? "grabbing" : "grab")};
  touch-action: none;
`;

const DirectionIndicator = styled(FontAwesomeIcon)`
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  color: #004fec;
  font-size: 18px;
  z-index: 4;
  pointer-events: none;
`;

const getClientX = (event) => {
  if (typeof event?.clientX === "number") {
    return event.clientX;
  }

  if (event?.touches?.[0]) {
    return event.touches[0].clientX;
  }

  if (event?.changedTouches?.[0]) {
    return event.changedTouches[0].clientX;
  }

  return null;
};

const clampNumber = (value, min, max) => Math.min(Math.max(value, min), max);

const Viewport = ({
  currentLeft,
  plotWidth,
  totalWidth,
  viewportWidth,
  onViewportChange,
}) => {
  const dragStateRef = useRef(null);
  const rafRef = useRef(null);
  const pendingLeftRef = useRef(currentLeft);

  // Only used for cursor CSS (grabbing vs grab). Not used to gate listeners.
  const [isDragging, setIsDragging] = React.useState(false);

  // Refs synced every render so stable callbacks always read the latest values.
  const maxTrackLeftRef = useRef(0);
  const maxLeftPxRef = useRef(0);
  const viewportWidthRef = useRef(viewportWidth);
  const onViewportChangeRef = useRef(onViewportChange);
  const currentLeftRef = useRef(currentLeft);
  const totalWidthRef = useRef(totalWidth);

  const maxTrackLeft = Math.max(totalWidth - viewportWidth, 0);
  const viewportWidthPx = useMemo(() => {
    if (totalWidth <= 0 || viewportWidth >= totalWidth) {
      return plotWidth;
    }

    return Math.min(
      plotWidth,
      Math.max((viewportWidth / totalWidth) * plotWidth, 32)
    );
  }, [plotWidth, totalWidth, viewportWidth]);
  const maxLeftPx = Math.max(plotWidth - viewportWidthPx, 0);
  const viewportLeftPx =
    maxTrackLeft > 0 ? (currentLeft / maxTrackLeft) * maxLeftPx : 0;

  maxTrackLeftRef.current = maxTrackLeft;
  maxLeftPxRef.current = maxLeftPx;
  viewportWidthRef.current = viewportWidth;
  onViewportChangeRef.current = onViewportChange;
  currentLeftRef.current = currentLeft;
  totalWidthRef.current = totalWidth;
  pendingLeftRef.current = currentLeft;

  // Stable emit — reads all dynamic values via refs, never becomes stale.
  const emitViewportChange = useCallback((nextLeft) => {
    const maxTL = maxTrackLeftRef.current;
    pendingLeftRef.current = clampNumber(nextLeft, 0, maxTL);

    if (rafRef.current !== null) {
      return;
    }

    rafRef.current = requestAnimationFrame(() => {
      rafRef.current = null;
      const clampedLeft = clampNumber(pendingLeftRef.current, 0, maxTrackLeftRef.current);
      onViewportChangeRef.current(clampedLeft, clampedLeft + viewportWidthRef.current);
    });
  }, []);

  // Stable drag-move handler — reads delta via refs.
  const updateDragFromClientX = useCallback((clientX) => {
    const dragState = dragStateRef.current;
    if (!dragState || clientX === null) {
      return;
    }

    const deltaPx = clientX - dragState.startClientX;
    const maxLP = maxLeftPxRef.current;
    const maxTL = maxTrackLeftRef.current;
    const deltaTrack = maxLP > 0 ? (deltaPx / maxLP) * maxTL : 0;
    emitViewportChange(Math.round(dragState.startLeft + deltaTrack));
  }, [emitViewportChange]);

  // --- Stable event listener trampolines ---
  // Created once via useRef(...).current so addEventListener / removeEventListener
  // always receive the identical function reference.

  const stableHandleMouseMove = useRef((event) =>
    updateDragFromClientX(getClientX(event))
  ).current;

  const stableHandleTouchMove = useRef((event) => {
    event.preventDefault();
    updateDragFromClientX(getClientX(event));
  }).current;

  // stopImplRef holds the implementation; updated every render so the
  // trampoline below always calls the up-to-date version.
  const stopImplRef = useRef(null);

  // stableStopDragging is the SAME function reference across all renders —
  // safe to use as both addEventListener and removeEventListener argument.
  const stableStopDragging = useRef(() => stopImplRef.current?.()).current;

  // Update the implementation each render, capturing the stable trampolines.
  stopImplRef.current = () => {
    dragStateRef.current = null;
    document.removeEventListener("mousemove", stableHandleMouseMove);
    document.removeEventListener("touchmove", stableHandleTouchMove);
    document.removeEventListener("mouseup", stableStopDragging);
    document.removeEventListener("touchend", stableStopDragging);
    document.removeEventListener("touchcancel", stableStopDragging);
    setIsDragging(false);
  };

  // Register listeners IMMEDIATELY on mousedown — bypasses the React render
  // cycle so no mousemove events are lost before listeners are active.
  const startDragging = useCallback((clientX) => {
    if (clientX === null || viewportWidthRef.current >= totalWidthRef.current) {
      return;
    }

    dragStateRef.current = {
      startClientX: clientX,
      startLeft: currentLeftRef.current,
    };

    document.addEventListener("mousemove", stableHandleMouseMove);
    document.addEventListener("mouseup", stableStopDragging);
    document.addEventListener("touchmove", stableHandleTouchMove, { passive: false });
    document.addEventListener("touchend", stableStopDragging);
    document.addEventListener("touchcancel", stableStopDragging);

    setIsDragging(true);
  }, [stableHandleMouseMove, stableHandleTouchMove, stableStopDragging]);

  // Remove all listeners and cancel any pending RAF on unmount.
  useEffect(() => () => {
    document.removeEventListener("mousemove", stableHandleMouseMove);
    document.removeEventListener("touchmove", stableHandleTouchMove);
    document.removeEventListener("mouseup", stableStopDragging);
    document.removeEventListener("touchend", stableStopDragging);
    document.removeEventListener("touchcancel", stableStopDragging);
    if (rafRef.current !== null) {
      cancelAnimationFrame(rafRef.current);
    }
  }, [stableHandleMouseMove, stableHandleTouchMove, stableStopDragging]);

  const jumpViewport = useCallback(
    (clientX, containerRect) => {
      if (clientX === null || viewportWidthRef.current >= totalWidthRef.current) {
        return;
      }

      const maxLP = maxLeftPxRef.current;
      const maxTL = maxTrackLeftRef.current;
      const vpWidthPx =
        totalWidthRef.current > 0
          ? Math.min(
              plotWidth,
              Math.max(
                (viewportWidthRef.current / totalWidthRef.current) * plotWidth,
                32
              )
            )
          : plotWidth;

      const relativePx = clampNumber(
        clientX - containerRect.left - vpWidthPx / 2,
        0,
        maxLP
      );
      const nextLeft = maxLP > 0 ? (relativePx / maxLP) * maxTL : 0;
      emitViewportChange(Math.round(nextLeft));
    },
    [emitViewportChange, plotWidth]
  );

  const handleTrackMouseDown = useCallback(
    (event) => {
      if (event.target !== event.currentTarget) {
        return;
      }

      event.preventDefault();
      jumpViewport(getClientX(event), event.currentTarget.getBoundingClientRect());
    },
    [jumpViewport]
  );

  const handleTrackTouchStart = useCallback(
    (event) => {
      if (event.target !== event.currentTarget) {
        return;
      }

      jumpViewport(getClientX(event), event.currentTarget.getBoundingClientRect());
    },
    [jumpViewport]
  );

  const handleWindowMouseDown = useCallback(
    (event) => {
      event.preventDefault();
      event.stopPropagation();
      startDragging(getClientX(event));
    },
    [startDragging]
  );

  const handleWindowTouchStart = useCallback(
    (event) => {
      event.stopPropagation();
      startDragging(getClientX(event));
    },
    [startDragging]
  );

  return (
    <ViewportTrack
      onMouseDown={handleTrackMouseDown}
      onTouchStart={handleTrackTouchStart}
    >
      <ViewportWindow
        $left={viewportLeftPx}
        $width={viewportWidthPx}
        $dragging={isDragging}
        onMouseDown={handleWindowMouseDown}
        onTouchStart={handleWindowTouchStart}
        className="drag-indicator-viewer-player"
      />
    </ViewportTrack>
  );
};

const EcgNavigator = ({
  width,
  height,
  railWidth,
  totalWidth,
  viewportWidth,
  currentLeft,
  onViewportChange,
  repository,
  labelWidth = 72,
}) => {
  const canScrollLeft = currentLeft > 0;
  const canScrollRight = currentLeft + viewportWidth < totalWidth;
  const plotWidth = Math.max(width - railWidth, 1);

  return (
    <NavigatorRoot>
      <ECG
        style={{
          background: "white",
        }}
        canvasId={"dii-miniature"}
        backgroundGrid={false}
        derivation={"dii-miniature"}
        dataDerivation={"dii"}
        canPin={false}
        label={"DII"}
        height={height}
        width={width}
        railWidth={railWidth}
        repository={repository}
        labelWidth={labelWidth}
      >
        <Viewport
          currentLeft={currentLeft}
          plotWidth={plotWidth}
          totalWidth={totalWidth}
          viewportWidth={viewportWidth}
          onViewportChange={onViewportChange}
        />
      </ECG>
      {canScrollLeft && (
        <DirectionIndicator icon={faArrowLeft} style={{ left: railWidth + 10 }} />
      )}
      {canScrollRight && (
        <DirectionIndicator icon={faArrowRight} style={{ right: "10px" }} />
      )}
    </NavigatorRoot>
  );
};

export default EcgNavigator;
