import React, {
  useEffect,
  useRef,
  useState,
  useCallback,
  ReactNode,
  forwardRef,
} from "react";
import "../../styles/TouchableLayout.css";

interface TouchableLayoutProps {
  children?: ReactNode;
  className?: string;
  maxHeight?: string | number;
}

function useThumbSync(scrollableRef: React.RefObject<HTMLDivElement | null>) {
  const [state, setState] = useState({
    height: 60,
    top: 0,
    hasScroll: false,
  });
  const rafRef = useRef<number | null>(null);

  const update = useCallback(() => {
    const el = scrollableRef.current;
    if (!el) return;

    const sh = el.scrollHeight;
    const ch = el.clientHeight;
    const st = el.scrollTop;
    const th = ch;

    if (sh <= ch) {
      setState({ height: th, top: 0, hasScroll: false });
      return;
    }

    const thumbHeight = Math.max(60, (ch / sh) * th);
    const range = th - thumbHeight;
    const scrollRange = sh - ch;
    const top = range <= 0 ? 0 : (st / scrollRange) * range;

    setState({ height: thumbHeight, top, hasScroll: true });
  }, []);

  useEffect(() => {
    const el = scrollableRef.current;
    if (!el) return;

    const schedule = () => {
      if (rafRef.current != null) return;
      rafRef.current = requestAnimationFrame(() => {
        rafRef.current = null;
        update();
      });
    };

    el.addEventListener("scroll", schedule, { passive: true });
    const ro = new ResizeObserver(schedule);
    ro.observe(el);
    schedule();

    return () => {
      el.removeEventListener("scroll", schedule);
      ro.disconnect();
      if (rafRef.current != null) cancelAnimationFrame(rafRef.current);
    };
  }, [update]);

  return state;
}

export const TouchableLayout = forwardRef<HTMLDivElement, TouchableLayoutProps>(
  ({ children, className, maxHeight }, ref) => {
    const containerRef = useRef<HTMLDivElement>(null);
    const scrollableRef = useRef<HTMLDivElement>(null);
    const trackRef = useRef<HTMLDivElement>(null);
    const thumbRef = useRef<HTMLDivElement>(null);

    const thumb = useThumbSync(scrollableRef);

    const setRefs = useCallback(
      (node: HTMLDivElement | null) => {
        (
          containerRef as React.MutableRefObject<HTMLDivElement | null>
        ).current = node;
        if (typeof ref === "function") {
          ref(node);
        } else if (ref) {
          (ref as React.MutableRefObject<HTMLDivElement | null>).current = node;
        }
      },
      [ref],
    );

    useEffect(() => {
      const scrollable = scrollableRef.current;
      if (!scrollable) return;

      let isDown = false;
      let isDragging = false;
      let startY = 0;
      let startScrollTop = 0;
      const DRAG_THRESHOLD = 5;

      const handlePointerDown = (e: Event) => {
        const pe = e as PointerEvent;

        isDown = true;
        isDragging = false;
        startY = pe.clientY;
        startScrollTop = scrollable.scrollTop;
      };

      const handlePointerMove = (e: Event) => {
        if (!isDown) return;
        const pe = e as PointerEvent;

        const walk = pe.clientY - startY;

        if (!isDragging && Math.abs(walk) < DRAG_THRESHOLD) {
          return;
        }

        if (!isDragging) {
          isDragging = true;
          scrollable.setPointerCapture(pe.pointerId);
        }

        pe.preventDefault();

        scrollable.scrollTop = Math.max(
          0,
          Math.min(
            scrollable.scrollHeight - scrollable.clientHeight,
            startScrollTop - walk,
          ),
        );
      };

      const stopScrolling = (e: Event) => {
        if (!isDown) return;
        const pe = e as PointerEvent;

        if (isDragging) {
          pe.preventDefault();
          isDragging = false;
        }

        isDown = false;
        try {
          scrollable.releasePointerCapture(pe.pointerId);
        } catch {
        }
      };

      scrollable.addEventListener("pointerdown", handlePointerDown);
      scrollable.addEventListener("pointermove", handlePointerMove);
      scrollable.addEventListener("pointerup", stopScrolling);
      scrollable.addEventListener("pointercancel", stopScrolling);

      return () => {
        scrollable.removeEventListener("pointerdown", handlePointerDown);
        scrollable.removeEventListener("pointermove", handlePointerMove);
        scrollable.removeEventListener("pointerup", stopScrolling);
        scrollable.removeEventListener("pointercancel", stopScrolling);
      };
    }, []);

    useEffect(() => {
      if (!thumb.hasScroll) return;

      const scrollable = scrollableRef.current;
      const track = trackRef.current;
      const thumbEl = thumbRef.current;
      if (!scrollable || !track || !thumbEl) return;

      let isDragging = false;
      let startY = 0;
      let startScrollTop = 0;

      const onPointerDown = (e: PointerEvent) => {
        e.preventDefault();
        isDragging = true;
        thumbEl.setPointerCapture(e.pointerId);
        startY = e.clientY;
        startScrollTop = scrollable.scrollTop;
      };

      const onPointerMove = (e: PointerEvent) => {
        if (!isDragging) return;
        e.preventDefault();
        const th = track.offsetHeight;
        const thumbH = thumbEl.offsetHeight;
        const range = th - thumbH;
        const scrollRange = scrollable.scrollHeight - scrollable.clientHeight;
        if (range <= 0 || scrollRange <= 0) return;

        const dy = e.clientY - startY;
        const dScroll = (dy / range) * scrollRange;
        scrollable.scrollTop = Math.max(
          0,
          Math.min(scrollRange, startScrollTop + dScroll),
        );
      };

      const onPointerUp = (e: PointerEvent) => {
        if (isDragging) {
          isDragging = false;
          thumbEl.releasePointerCapture(e.pointerId);
        }
      };

      thumbEl.addEventListener("pointerdown", onPointerDown as EventListener);
      thumbEl.addEventListener("pointermove", onPointerMove as EventListener);
      thumbEl.addEventListener("pointerup", onPointerUp as EventListener);
      thumbEl.addEventListener("pointercancel", onPointerUp as EventListener);

      return () => {
        thumbEl.removeEventListener(
          "pointerdown",
          onPointerDown as EventListener,
        );
        thumbEl.removeEventListener(
          "pointermove",
          onPointerMove as EventListener,
        );
        thumbEl.removeEventListener("pointerup", onPointerUp as EventListener);
        thumbEl.removeEventListener(
          "pointercancel",
          onPointerUp as EventListener,
        );
      };
    }, [thumb.hasScroll]);

    const containerClassName = className
      ? `scrollable-container ${className}`
      : "scrollable-container";

    const viewportStyle: React.CSSProperties = maxHeight
      ? {
          maxHeight:
            typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight,
        }
      : {};

    return (
      <div ref={setRefs} className={containerClassName}>
        <div className="scrollable-viewport" style={viewportStyle}>
          <div ref={scrollableRef} className="scrollable">
            {children}
          </div>
          {thumb.hasScroll && (
            <div ref={trackRef} className="custom-scrollbar-track">
              <div
                ref={thumbRef}
                className="custom-scrollbar-thumb"
                style={{ height: thumb.height, top: thumb.top }}
              />
            </div>
          )}
        </div>
      </div>
    );
  },
);

TouchableLayout.displayName = "TouchableLayout";
