import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
import { computeOverlayScrollbarThumbMeta } from '@/lib/dom/overlayScrollbarMetrics';
import { bindOverlayScrollbarThumbDrag } from '@/lib/dom/overlayScrollbarThumb';
import { usePerfProbeFlags } from '@/lib/perf/perfFlags';

export type OverlayScrollRailInset = 'none' | 'mini' | 'panel';

export type OverlayScrollAreaProps = {
  children: React.ReactNode;
  /** Optional handler on the outer wrapper (e.g. mini queue DnD hit-testing). */
  onMouseMove?: React.MouseEventHandler<HTMLDivElement>;
  /** Classes on the outer wrapper (e.g. queue-list-wrap, mini-queue-wrap). */
  className?: string;
  /** Classes on the scrollable viewport (e.g. queue-list, mini-queue). */
  viewportClassName?: string;
  /** Serialized internally — triggers remeasure + ResizeObserver refresh. */
  measureDeps?: ReadonlyArray<unknown>;
  /** Vertical inset of the hit rail (align with viewport padding). */
  railInset?: OverlayScrollRailInset;
  /** e.g. during native DnD — scroll-behavior: auto on the viewport. */
  viewportScrollBehaviorAuto?: boolean;
  /** Ref to the scrollable element (querySelector, scrollIntoView, etc.). */
  viewportRef?: React.Ref<HTMLDivElement>;
  /** Ref to the outer wrapper (incl. overlay scrollbar rail). */
  wrapRef?: React.Ref<HTMLDivElement>;
  /** Optional id on the viewport (e.g. main app scroll for route pages). */
  viewportId?: string;
  /** Optional wheel handler on the scrollable viewport. */
  viewportOnWheel?: React.WheelEventHandler<HTMLDivElement>;
  /** Optional touch-move handler on the scrollable viewport. */
  viewportOnTouchMove?: React.TouchEventHandler<HTMLDivElement>;
};

const RAIL_INSET_CLASS: Record<OverlayScrollRailInset, string> = {
  none: 'overlay-scroll--rail-inset-none',
  mini: 'overlay-scroll--rail-inset-mini',
  panel: 'overlay-scroll--rail-inset-panel',
};

function assignRef<T>(ref: React.Ref<T> | undefined, value: T) {
  if (ref == null) return;
  if (typeof ref === 'function') ref(value);
  else (ref as { current: T | null }).current = value;
}

export default function OverlayScrollArea({
  children,
  onMouseMove,
  className = '',
  viewportClassName = '',
  measureDeps = [],
  railInset = 'none',
  viewportScrollBehaviorAuto = false,
  viewportRef: viewportRefProp,
  wrapRef: wrapRefProp,
  viewportId,
  viewportOnWheel,
  viewportOnTouchMove,
}: OverlayScrollAreaProps) {
  const perfFlags = usePerfProbeFlags();
  const wrapRef = useRef<HTMLDivElement | null>(null);
  const viewportRef = useRef<HTMLDivElement | null>(null);
  /** Coalesce burst scroll events to one thumb update per animation frame. */
  const scrollRecomputeRafRef = useRef(0);
  const [meta, setMeta] = useState({ thumbH: 0, thumbT: 0, visible: false });

  const recompute = useCallback(() => {
    const vp = viewportRef.current;
    const wrap = wrapRef.current;
    const rail = wrap?.querySelector<HTMLElement>('.overlay-scroll__rail');
    const trackH =
      rail && rail.clientHeight > 0 ? rail.clientHeight : undefined;
    setMeta(computeOverlayScrollbarThumbMeta(vp, trackH));
  }, []);

  const scheduleScrollRecompute = useCallback(() => {
    if (scrollRecomputeRafRef.current !== 0) return;
    scrollRecomputeRafRef.current = requestAnimationFrame(() => {
      scrollRecomputeRafRef.current = 0;
      recompute();
    });
  }, [recompute]);

  useEffect(() => () => {
    if (scrollRecomputeRafRef.current !== 0) {
      cancelAnimationFrame(scrollRecomputeRafRef.current);
      scrollRecomputeRafRef.current = 0;
    }
  }, []);

  const measureKey = JSON.stringify(measureDeps ?? []);

  useLayoutEffect(() => {
    if (perfFlags.disableOverlayScrollbars) return;
    if (!meta.visible) return;
    const vp = viewportRef.current;
    const wrap = wrapRef.current;
    const rail = wrap?.querySelector<HTMLElement>('.overlay-scroll__rail');
    const th = rail?.clientHeight;
    if (!vp || !th || th <= 0) return;
    setMeta((prev) => {
      const next = computeOverlayScrollbarThumbMeta(vp, th);
      if (
        prev.thumbH === next.thumbH &&
        prev.thumbT === next.thumbT &&
        prev.visible === next.visible
      ) {
        return prev;
      }
      return next;
    });
  }, [meta.visible, perfFlags.disableOverlayScrollbars]);

  useEffect(() => {
    if (perfFlags.disableOverlayScrollbars) return;
    recompute();
    const wrap = wrapRef.current;
    const onWinResize = () => recompute();
    window.addEventListener('resize', onWinResize);
    const ro =
      typeof ResizeObserver !== 'undefined' && wrap
        ? new ResizeObserver(() => recompute())
        : null;
    if (ro && wrap) ro.observe(wrap);
    return () => {
      window.removeEventListener('resize', onWinResize);
      ro?.disconnect();
    };
  }, [recompute, measureKey, perfFlags.disableOverlayScrollbars]);

  const setViewportNode = (el: HTMLDivElement | null) => {
    viewportRef.current = el;
    assignRef(viewportRefProp, el);
  };

  const setWrapNode = (el: HTMLDivElement | null) => {
    wrapRef.current = el;
    assignRef(wrapRefProp, el);
  };

  const rootClass = [
    'overlay-scroll',
    RAIL_INSET_CLASS[railInset],
    viewportScrollBehaviorAuto ? 'overlay-scroll--viewport-scroll-auto' : '',
    className,
  ]
    .filter(Boolean)
    .join(' ');

  const viewportClass = ['overlay-scroll__viewport', viewportClassName].filter(Boolean).join(' ');

  return (
    <div ref={setWrapNode} className={rootClass} onMouseMove={onMouseMove}>
      <div
        id={viewportId}
        ref={setViewportNode}
        className={viewportClass}
        onScroll={perfFlags.disableOverlayScrollbars ? undefined : scheduleScrollRecompute}
        onWheel={viewportOnWheel}
        onTouchMove={viewportOnTouchMove}
      >
        {children}
      </div>
      {!perfFlags.disableOverlayScrollbars && meta.visible && (
        <div className="overlay-scroll__rail" aria-hidden>
          <div
            className="overlay-scroll__thumb"
            style={{
              height: `${meta.thumbH}px`,
              transform: `translateY(${meta.thumbT}px)`,
            }}
            onPointerDown={(ev) => bindOverlayScrollbarThumbDrag(ev, viewportRef.current)}
          />
        </div>
      )}
    </div>
  );
}
