/**
 * Vertical drag handle between two panes (spec §01A).
 *
 * Sits between a side pane and the editor; dragging resizes the side pane while
 * the editor absorbs the change, so the opposite side pane is unaffected. Width
 * is clamped (pure `clampSideWidth`) so neither the dragged pane nor the editor
 * falls below its minimum.
 *
 * Accessibility follows WAI-ARIA APG → Window Splitter (see
 * docs/architecture/10 §5): a focusable `separator` that reports the controlled
 * pane's size as `aria-valuenow` on a 0–100 scale (0 = min, 100 = max) and is
 * driven by ←/→ to nudge plus Home/End to jump to the pane's min/max. (Enter to
 * collapse waits for the M6 pane-visibility model — there's nothing to collapse
 * to yet.)
 *
 * The handle reads the panes-row width from its own parent: lazily during a
 * gesture (most accurate mid-drag), and via a ResizeObserver for the reactive
 * `aria-valuenow` so the announced size tracks window/container resizes too.
 */

import { useLayoutEffect, useRef, useState } from 'react';
import { useResizeDrag } from '../hooks/useResizeDrag';
import { clampSideWidth, sideWidthValue, usePanesStore, type PaneSide } from '../stores/PanesStore';
import styles from './ResizeHandle.module.css';

/** Keyboard nudge step (px) per arrow press. */
const KEY_STEP = 16;

interface ResizeHandleProps {
  /** Which side pane this handle resizes. */
  side: PaneSide;
  /** Accessible label, e.g. "Resize snippet library". */
  label: string;
}

export function ResizeHandle({ side, label }: ResizeHandleProps) {
  const ref = useRef<HTMLDivElement>(null);

  // This side pane's width and the opposite side pane's, reactively — so the
  // reported value recomputes as either changes.
  const width = usePanesStore((s) => (side === 'library' ? s.libraryWidth : s.previewWidth));
  const otherWidth = usePanesStore((s) => (side === 'library' ? s.previewWidth : s.libraryWidth));

  // Observe the panes-row width so aria-valuenow stays correct across window and
  // container resizes, not only pane drags.
  const [containerWidth, setContainerWidth] = useState(0);
  useLayoutEffect(() => {
    const parent = ref.current?.parentElement;
    if (!parent) return;
    setContainerWidth(parent.clientWidth);
    if (typeof ResizeObserver === 'undefined') return;
    const ro = new ResizeObserver(() => setContainerWidth(parent.clientWidth));
    ro.observe(parent);
    return () => ro.disconnect();
  }, []);

  /** Live panes-row width for imperative drag/key math (most accurate in-gesture). */
  const readContainerWidth = (): number => ref.current?.parentElement?.clientWidth ?? 0;

  /** Apply a desired width for this side, clamped against the current layout. */
  const applyWidth = (desired: number) => {
    const { libraryWidth, previewWidth, setWidth } = usePanesStore.getState();
    const other = side === 'library' ? previewWidth : libraryWidth;
    setWidth(side, clampSideWidth(side, desired, readContainerWidth(), other));
  };

  // The left handle grows its pane as it moves right; the right handle (left
  // of the preview) shrinks the preview as it moves right.
  const onPointerDown = useResizeDrag(
    'x',
    () =>
      side === 'library'
        ? usePanesStore.getState().libraryWidth
        : usePanesStore.getState().previewWidth,
    (startWidth, delta) => applyWidth(side === 'library' ? startWidth + delta : startWidth - delta),
  );

  // Keyboard model per WAI-ARIA APG → Window Splitter: arrows nudge; Home/End jump
  // to the pane's smallest/largest allowed size (clampSideWidth caps the extremes).
  const onKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
    const current =
      side === 'library'
        ? usePanesStore.getState().libraryWidth
        : usePanesStore.getState().previewWidth;
    switch (e.key) {
      case 'ArrowLeft':
      case 'ArrowRight': {
        const dir = e.key === 'ArrowRight' ? 1 : -1;
        applyWidth(current + (side === 'library' ? dir * KEY_STEP : -dir * KEY_STEP));
        break;
      }
      case 'Home': // smallest primary-pane size
        applyWidth(0);
        break;
      case 'End': // largest primary-pane size
        applyWidth(Number.MAX_SAFE_INTEGER);
        break;
      default:
        return; // not ours — let it bubble
    }
    e.preventDefault();
  };

  const valueNow = sideWidthValue(side, width, containerWidth, otherWidth);

  return (
    <div
      ref={ref}
      className={styles.handle}
      role="separator"
      aria-orientation="vertical"
      aria-label={label}
      // The splitter controls — and reports the size of — its side pane (APG).
      aria-controls={`pane-${side}`}
      aria-valuenow={valueNow ?? undefined}
      aria-valuemin={valueNow === null ? undefined : 0}
      aria-valuemax={valueNow === null ? undefined : 100}
      aria-valuetext={valueNow === null ? undefined : `${valueNow}%`}
      tabIndex={0}
      onPointerDown={onPointerDown}
      onKeyDown={onKeyDown}
    >
      <span className={styles.grip} aria-hidden="true" />
    </div>
  );
}
