import { useMemo, useRef, useCallback, useEffect, useLayoutEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import type { ArtMovement } from '../types';
import { MOVEMENT_LINEAGE } from '../data/movement-lineage';
import { panTimelineView, zoomTimelineView } from '../utils/timelineView';
import { vividMovementColor } from '../utils/movementColor';
import './VerticalMovementBands.css';

interface Props {
  movements: ArtMovement[];
  viewStart: number;
  viewEnd: number;
  absoluteMin: number;
  absoluteMax: number;
  onViewChange: (start: number, end: number) => void;
  onMovementClick?: (movementId: number) => void;
}

interface VLayout {
  movement: ArtMovement;
  /** Year span as % from bottom (earlier = lower). */
  yStart: number;
  yEnd: number;
  /** Lane center as % from left. */
  x: number;
  strokePx: number;
  displayColor: string;
  parentIds: number[];
}

interface VBranch {
  key: string;
  d: string;
  colorFrom: string;
  colorTo: string;
  strokePx: number;
  fromId: number;
  toId: number;
  x1: number;
  y1: number;
  x2: number;
  y2: number;
}

const MAX_STROKE = 108; // ~300% of prior max (36)
const MIN_STROKE = 54; // ~300% of prior min (18)
const SIDE_PAD = 24;
/** Preferred centre-to-centre spacing: stroke + small gap (keeps columns tight). */
const PREFERRED_LANE_PITCH = MAX_STROKE + 16;
const LANE_MIN_GAP_YEARS = 2;

function yearToBottomPercent(year: number, start: number, end: number): number {
  return ((year - start) / (end - start)) * 100;
}

function buildLineageParentMap(
  visible: ArtMovement[],
  nameToId: Map<string, number>
): Map<number, number[]> {
  const parents = new Map<number, number[]>();
  const visibleIds = new Set(visible.map((m) => m.id));
  for (const [parentName, childName] of MOVEMENT_LINEAGE) {
    const parentId = nameToId.get(parentName);
    const childId = nameToId.get(childName);
    if (parentId == null || childId == null) continue;
    if (!visibleIds.has(parentId) || !visibleIds.has(childId)) continue;
    const list = parents.get(childId) || [];
    if (!list.includes(parentId)) list.push(parentId);
    parents.set(childId, list);
  }
  return parents;
}

function assignDepths(
  group: ArtMovement[],
  lineageParents: Map<number, number[]>
): Map<number, number> {
  const depths = new Map<number, number>();
  const visiting = new Set<number>();
  const visit = (id: number): number => {
    if (depths.has(id)) return depths.get(id)!;
    if (visiting.has(id)) return 0;
    visiting.add(id);
    const parents = lineageParents.get(id) || [];
    const d = parents.length ? 1 + Math.max(...parents.map(visit)) : 0;
    visiting.delete(id);
    depths.set(id, d);
    return d;
  };
  for (const m of group) visit(m.id);
  return depths;
}

/** Prefer center, then alternate right / left: 0, +1, -1, +2, -2, … */
function centerOutOffsets(max = 64): number[] {
  const out = [0];
  for (let d = 1; d <= max; d++) out.push(d, -d);
  return out;
}

function pickNearestFreeLane(preferred: number, isFree: (lane: number) => boolean): number {
  for (const delta of centerOutOffsets()) {
    const lane = preferred + delta;
    if (isFree(lane)) return lane;
  }
  return preferred;
}

/** Collapse signed lane indices to contiguous 0..n-1 (left → right). */
function compactSignedLanes(laneById: Map<number, number>): void {
  const usedSorted = [...new Set(laneById.values())].sort((a, b) => a - b);
  const remap = new Map(usedSorted.map((lane, index) => [lane, index]));
  for (const [id, lane] of laneById) {
    laneById.set(id, remap.get(lane) ?? 0);
  }
}

function assignTemporalLanes(
  group: ArtMovement[],
  viewStart: number,
  viewEnd: number,
  lineageParents: Map<number, number[]>
): Map<number, number> {
  const depths = assignDepths(group, lineageParents);
  const spans = group
    .map((m) => ({
      id: m.id,
      start: Math.max(m.start_year, viewStart),
      end: Math.min(m.end_year, viewEnd),
      depth: depths.get(m.id) ?? 0,
    }))
    .filter((s) => s.end > s.start)
    .sort((a, b) => a.depth - b.depth || a.start - b.start || a.end - b.end);

  /** Signed lane → year when that lane frees up. */
  const laneEnds = new Map<number, number>();
  const laneById = new Map<number, number>();

  for (const span of spans) {
    const parentLanes = (lineageParents.get(span.id) || [])
      .map((pid) => laneById.get(pid))
      .filter((lane): lane is number => lane != null);
    const preferred =
      parentLanes.length > 0
        ? Math.round(parentLanes.reduce((s, l) => s + l, 0) / parentLanes.length)
        : 0;

    const lane = pickNearestFreeLane(preferred, (candidate) => {
      const end = laneEnds.get(candidate);
      return end == null || end + LANE_MIN_GAP_YEARS <= span.start;
    });
    laneEnds.set(lane, span.end);
    laneById.set(span.id, lane);
  }
  compactSignedLanes(laneById);
  return laneById;
}

/**
 * Prefer one column per movement when the canvas is wide enough, so streams
 * do not stack in the same vertical lane. Fall back to temporal packing only
 * when there is not enough horizontal room.
 *
 * Lanes grow from the center outward (0, +1, −1, …) so the layout reads as a tree.
 */
function assignVerticalLanes(
  group: ArtMovement[],
  viewStart: number,
  viewEnd: number,
  lineageParents: Map<number, number[]>,
  canvasWidth: number
): Map<number, number> {
  const usable = Math.max(200, canvasWidth - SIDE_PAD * 2);
  const minLanePx = PREFERRED_LANE_PITCH;
  const maxExclusive = Math.max(1, Math.floor(usable / minLanePx));

  if (group.length <= maxExclusive) {
    const depths = assignDepths(group, lineageParents);
    // Roots first so the trunk claims center; children then fan around parents.
    const sorted = [...group].sort(
      (a, b) =>
        (depths.get(a.id) ?? 0) - (depths.get(b.id) ?? 0) ||
        a.start_year - b.start_year ||
        a.name.localeCompare(b.name)
    );
    const laneById = new Map<number, number>();
    const used = new Set<number>();
    for (const m of sorted) {
      const parentLanes = (lineageParents.get(m.id) || [])
        .map((pid) => laneById.get(pid))
        .filter((lane): lane is number => lane != null);
      const preferred =
        parentLanes.length > 0
          ? Math.round(parentLanes.reduce((s, l) => s + l, 0) / parentLanes.length)
          : 0;
      const lane = pickNearestFreeLane(preferred, (candidate) => !used.has(candidate));
      used.add(lane);
      laneById.set(m.id, lane);
    }
    compactSignedLanes(laneById);
    return laneById;
  }

  return assignTemporalLanes(group, viewStart, viewEnd, lineageParents);
}

function influenceCount(m: ArtMovement): number {
  const n = m.influence_link_count;
  return typeof n === 'number' && Number.isFinite(n) ? Math.max(0, n) : 0;
}

function pctToSvg(xPct: number, yBottomPct: number, widthPx: number, heightPx: number) {
  return {
    x: (xPct / 100) * widthPx,
    y: ((100 - yBottomPct) / 100) * heightPx,
  };
}

/** Vertical stream path: time along Y (SVG y grows down → invert bottom%). */
function verticalStreamPath(
  xPct: number,
  yStartPct: number,
  yEndPct: number,
  heightPx: number,
  widthPx: number
): string {
  const start = pctToSvg(xPct, yEndPct, widthPx, heightPx);
  const end = pctToSvg(xPct, yStartPct, widthPx, heightPx);
  const midY = (start.y + end.y) / 2;
  const bulge = Math.min(18, Math.abs(end.y - start.y) * 0.06);
  return `M ${start.x} ${start.y} C ${start.x + bulge} ${midY}, ${end.x - bulge} ${midY}, ${end.x} ${end.y}`;
}

/** Absolute-year anchors so pan/scroll keeps a constant connection angle. */
function branchAnchorYears(
  parent: ArtMovement,
  child: ArtMovement,
  childIndex: number,
  childCount: number
): { originYear: number; targetYear: number } | null {
  const parentSpan = parent.end_year - parent.start_year;
  if (parentSpan <= 0) return null;

  const tBase = childCount === 1 ? 0.38 : 0.28 + (childIndex / Math.max(1, childCount - 1)) * 0.22;
  let originYear = parent.start_year + parentSpan * tBase;
  const targetYear = child.start_year;

  if (originYear >= targetYear) {
    originYear = Math.min(parent.start_year + parentSpan * 0.2, targetYear - 1);
  }
  originYear = Math.max(parent.start_year, Math.min(parent.end_year, originYear));
  if (originYear >= targetYear) return null;

  return { originYear, targetYear };
}

function branchPath(x1: number, y1: number, x2: number, y2: number): string {
  const dx = x2 - x1;
  const dy = y2 - y1;
  // Pull control points along the diagonal so the curve reads as a waterfall, not an L-stair.
  const c1x = x1 + dx * 0.35;
  const c1y = y1 + dy * 0.55;
  const c2x = x2 - dx * 0.25;
  const c2y = y2 - dy * 0.2;
  return `M ${x1} ${y1} C ${c1x} ${c1y}, ${c2x} ${c2y}, ${x2} ${y2}`;
}

/** Exponential chase rate (1/s) so lane / view shifts read as motion, not snaps. */
const LAYOUT_ANIM_RATE = 14;
const LAYOUT_ANIM_EPS = 0.06;

interface AnimatedVFlow {
  layouts: VLayout[];
  branches: VBranch[];
}

function lerp(a: number, b: number, t: number): number {
  return a + (b - a) * t;
}

function geomSettled(a: number, b: number, eps = LAYOUT_ANIM_EPS): boolean {
  return Math.abs(a - b) <= eps;
}

function lerpVLayout(from: VLayout, to: VLayout, t: number): VLayout {
  return {
    ...to,
    yStart: lerp(from.yStart, to.yStart, t),
    yEnd: lerp(from.yEnd, to.yEnd, t),
    x: lerp(from.x, to.x, t),
    strokePx: lerp(from.strokePx, to.strokePx, t),
  };
}

function vLayoutSettled(a: VLayout, b: VLayout): boolean {
  return (
    geomSettled(a.yStart, b.yStart) &&
    geomSettled(a.yEnd, b.yEnd) &&
    geomSettled(a.x, b.x) &&
    geomSettled(a.strokePx, b.strokePx, 0.35)
  );
}

function lerpVBranch(from: VBranch, to: VBranch, t: number): VBranch {
  const x1 = lerp(from.x1, to.x1, t);
  const y1 = lerp(from.y1, to.y1, t);
  const x2 = lerp(from.x2, to.x2, t);
  const y2 = lerp(from.y2, to.y2, t);
  const strokePx = lerp(from.strokePx, to.strokePx, t);
  return {
    ...to,
    x1,
    y1,
    x2,
    y2,
    strokePx,
    d: branchPath(x1, y1, x2, y2),
  };
}

function vBranchSettled(a: VBranch, b: VBranch): boolean {
  return (
    geomSettled(a.x1, b.x1) &&
    geomSettled(a.y1, b.y1) &&
    geomSettled(a.x2, b.x2) &&
    geomSettled(a.y2, b.y2) &&
    geomSettled(a.strokePx, b.strokePx, 0.35)
  );
}

function blendVFlow(
  from: AnimatedVFlow,
  to: AnimatedVFlow,
  t: number
): { next: AnimatedVFlow; settled: boolean } {
  const fromLayouts = new Map(from.layouts.map((l) => [l.movement.id, l]));
  const fromBranches = new Map(from.branches.map((b) => [b.key, b]));
  let settled = true;

  const layouts = to.layouts.map((target) => {
    const prev = fromLayouts.get(target.movement.id);
    if (!prev) return target;
    if (vLayoutSettled(prev, target)) return target;
    settled = false;
    return lerpVLayout(prev, target, t);
  });

  const branches = to.branches.map((target) => {
    const prev = fromBranches.get(target.key);
    if (!prev) return target;
    if (vBranchSettled(prev, target)) return target;
    settled = false;
    return lerpVBranch(prev, target, t);
  });

  return { next: { layouts, branches }, settled };
}

export default function VerticalMovementBands({
  movements,
  viewStart,
  viewEnd,
  absoluteMin,
  absoluteMax,
  onViewChange,
  onMovementClick,
}: Props) {
  const { t } = useTranslation('home');
  const canvasRef = useRef<HTMLDivElement>(null);
  const [canvasSize, setCanvasSize] = useState({ w: 800, h: 600 });
  const [panning, setPanning] = useState(false);
  const [hoveredMovementId, setHoveredMovementId] = useState<number | null>(null);
  const [flowVisual, setFlowVisual] = useState<AnimatedVFlow | null>(null);
  const panStart = useRef({ y: 0, viewStart: 0, viewEnd: 0 });
  const viewRef = useRef({ viewStart, viewEnd });
  const onViewChangeRef = useRef(onViewChange);
  const flowVisualRef = useRef<AnimatedVFlow | null>(null);
  const flowTargetRef = useRef<AnimatedVFlow | null>(null);
  const flowRafRef = useRef<number | null>(null);
  const flowLastTsRef = useRef(0);
  viewRef.current = { viewStart, viewEnd };
  onViewChangeRef.current = onViewChange;

  useLayoutEffect(() => {
    const el = canvasRef.current;
    if (!el) return;
    const measure = () => {
      const rect = el.getBoundingClientRect();
      if (rect.width > 0 && rect.height > 0) {
        setCanvasSize({ w: Math.round(rect.width), h: Math.round(rect.height) });
      }
    };
    measure();
    const ro = new ResizeObserver(measure);
    ro.observe(el);
    return () => ro.disconnect();
  }, []);

  const visibleMovements = useMemo(
    () => movements.filter((m) => m.end_year > viewStart && m.start_year < viewEnd),
    [movements, viewStart, viewEnd]
  );

  const { layouts, branches } = useMemo(() => {
    if (visibleMovements.length === 0) {
      return { layouts: [] as VLayout[], branches: [] as VBranch[] };
    }
    const nameToId = new Map(movements.map((m) => [m.name, m.id]));
    const lineageParents = buildLineageParentMap(visibleMovements, nameToId);
    const laneIndex = assignVerticalLanes(
      visibleMovements,
      viewStart,
      viewEnd,
      lineageParents,
      canvasSize.w
    );

    let maxLanes = 0;
    for (const m of visibleMovements) {
      const lane = laneIndex.get(m.id) ?? 0;
      maxLanes = Math.max(maxLanes, lane + 1);
    }
    maxLanes = Math.max(1, maxLanes);

    const usable = Math.max(200, canvasSize.w - SIDE_PAD * 2);
    // Pack columns tightly; only stretch if the canvas is narrower than the preferred cluster.
    const lanePitch = Math.min(usable / maxLanes, PREFERRED_LANE_PITCH);
    const clusterWidth = lanePitch * maxLanes;
    const startX = SIDE_PAD + Math.max(0, (usable - clusterWidth) / 2);
    const laneCentersPct: number[] = [];
    for (let lane = 0; lane < maxLanes; lane++) {
      const centerPx = startX + lanePitch * lane + lanePitch / 2;
      laneCentersPct[lane] = (centerPx / Math.max(1, canvasSize.w)) * 100;
    }

    const maxInf = Math.max(1, ...visibleMovements.map(influenceCount));
    const layoutById = new Map<number, VLayout>();
    for (const m of visibleMovements) {
      const yStart = yearToBottomPercent(Math.max(m.start_year, viewStart), viewStart, viewEnd);
      const yEnd = yearToBottomPercent(Math.min(m.end_year, viewEnd), viewStart, viewEnd);
      if (yEnd <= yStart) continue;
      const lane = laneIndex.get(m.id) ?? 0;
      const baseStroke =
        MIN_STROKE + (influenceCount(m) / maxInf) * (MAX_STROKE - MIN_STROKE);
      // Allow nearly full preferred stroke; only shrink if the pitch is forced smaller.
      const strokePx = Math.min(MAX_STROKE, Math.max(MIN_STROKE, baseStroke), lanePitch * 0.88);
      layoutById.set(m.id, {
        movement: m,
        yStart,
        yEnd,
        x: laneCentersPct[lane] ?? 50,
        strokePx,
        displayColor: vividMovementColor(m.color),
        parentIds: lineageParents.get(m.id) || [],
      });
    }

    const childIdsByParent = new Map<number, number[]>();
    for (const layout of layoutById.values()) {
      for (const parentId of layout.parentIds) {
        if (!layoutById.has(parentId)) continue;
        const children = childIdsByParent.get(parentId) || [];
        children.push(layout.movement.id);
        childIdsByParent.set(parentId, children);
      }
    }
    for (const children of childIdsByParent.values()) {
      children.sort((a, b) => {
        const la = layoutById.get(a)!;
        const lb = layoutById.get(b)!;
        return la.x - lb.x || la.movement.start_year - lb.movement.start_year;
      });
    }

    const branchList: VBranch[] = [];
    for (const layout of layoutById.values()) {
      for (const parentId of layout.parentIds) {
        const parent = layoutById.get(parentId);
        if (!parent) continue;
        const children = childIdsByParent.get(parentId) || [layout.movement.id];
        const childIndex = children.indexOf(layout.movement.id);
        const anchors = branchAnchorYears(
          parent.movement,
          layout.movement,
          childIndex,
          children.length
        );
        if (!anchors) continue;

        // Map fixed calendar years → current view % so pan keeps dx/dy (and angle) stable.
        const originY = yearToBottomPercent(anchors.originYear, viewStart, viewEnd);
        const targetY = yearToBottomPercent(anchors.targetYear, viewStart, viewEnd);
        const from = pctToSvg(parent.x, originY, canvasSize.w, canvasSize.h);
        const to = pctToSvg(layout.x, targetY, canvasSize.w, canvasSize.h);
        branchList.push({
          key: `${parentId}-${layout.movement.id}`,
          d: branchPath(from.x, from.y, to.x, to.y),
          colorFrom: parent.displayColor,
          colorTo: layout.displayColor,
          strokePx: Math.max(14, Math.min(parent.strokePx, layout.strokePx) * 0.55),
          fromId: parentId,
          toId: layout.movement.id,
          x1: from.x,
          y1: from.y,
          x2: to.x,
          y2: to.y,
        });
      }
    }

    return {
      layouts: [...layoutById.values()].sort((a, b) => a.movement.start_year - b.movement.start_year),
      branches: branchList,
    };
  }, [visibleMovements, movements, viewStart, viewEnd, canvasSize.w, canvasSize.h]);

  useLayoutEffect(() => {
    const target: AnimatedVFlow = { layouts, branches };
    flowTargetRef.current = target;

    if (!flowVisualRef.current) {
      flowVisualRef.current = target;
      setFlowVisual(target);
      return;
    }

    if (flowRafRef.current != null) return;

    flowLastTsRef.current = performance.now();
    const step = (now: number) => {
      const prev = flowVisualRef.current;
      const goal = flowTargetRef.current;
      if (!prev || !goal) {
        flowRafRef.current = null;
        return;
      }

      const dt = Math.min(0.048, Math.max(0, (now - flowLastTsRef.current) / 1000));
      flowLastTsRef.current = now;
      const t = 1 - Math.exp(-LAYOUT_ANIM_RATE * dt);
      const { next, settled } = blendVFlow(prev, goal, t);
      flowVisualRef.current = settled ? goal : next;
      setFlowVisual(flowVisualRef.current);

      if (settled) {
        flowRafRef.current = null;
        return;
      }
      flowRafRef.current = requestAnimationFrame(step);
    };
    flowRafRef.current = requestAnimationFrame(step);
  }, [layouts, branches]);

  useEffect(() => {
    return () => {
      if (flowRafRef.current != null) {
        cancelAnimationFrame(flowRafRef.current);
        flowRafRef.current = null;
      }
    };
  }, []);

  useEffect(() => {
    const el = canvasRef.current;
    if (!el) return;
    const onWheel = (e: WheelEvent) => {
      e.preventDefault();
      const rect = el.getBoundingClientRect();
      const { viewStart: vs, viewEnd: ve } = viewRef.current;
      const invertedClientY = rect.bottom - (e.clientY - rect.top);
      const next = zoomTimelineView(
        invertedClientY,
        0,
        rect.height,
        e.deltaY,
        vs,
        ve,
        absoluteMin,
        absoluteMax
      );
      onViewChangeRef.current(next.start, next.end);
    };
    el.addEventListener('wheel', onWheel, { passive: false, capture: true });
    return () => el.removeEventListener('wheel', onWheel, { capture: true });
  }, [absoluteMin, absoluteMax]);

  const handlePanStart = useCallback(
    (e: React.MouseEvent) => {
      if (e.button !== 0) return;
      e.preventDefault();
      setPanning(true);
      panStart.current = { y: e.clientY, viewStart, viewEnd };
    },
    [viewStart, viewEnd]
  );

  useEffect(() => {
    if (!panning) return;
    const onMove = (e: MouseEvent) => {
      const rect = canvasRef.current?.getBoundingClientRect();
      if (!rect) return;
      const dy = e.clientY - panStart.current.y;
      const next = panTimelineView(
        -dy,
        rect.height,
        panStart.current.viewStart,
        panStart.current.viewEnd,
        absoluteMin,
        absoluteMax
      );
      onViewChangeRef.current(next.start, next.end);
    };
    const onUp = () => setPanning(false);
    window.addEventListener('mousemove', onMove);
    window.addEventListener('mouseup', onUp);
    return () => {
      window.removeEventListener('mousemove', onMove);
      window.removeEventListener('mouseup', onUp);
    };
  }, [panning, absoluteMin, absoluteMax]);

  if (visibleMovements.length === 0) {
    return (
      <div className="vflow-empty">
        <p>No art movements in this time range. Zoom out to explore more periods.</p>
      </div>
    );
  }

  const displayLayouts = flowVisual?.layouts ?? layouts;
  const displayBranches = flowVisual?.branches ?? branches;

  return (
    <div className="vflow">
      <p className="vflow-caption">
        {t('captionVerticalFlow')}
      </p>
      <div
        ref={canvasRef}
        className={`vflow-canvas${panning ? ' vflow-panning' : ''}`}
        onMouseDown={handlePanStart}
      >
        <svg
          className="vflow-svg"
          viewBox={`0 0 ${canvasSize.w} ${canvasSize.h}`}
          preserveAspectRatio="none"
        >
          <defs>
            {displayBranches.map((branch) => (
              <linearGradient
                key={`grad-${branch.key}`}
                id={`vflow-branch-grad-${branch.key}`}
                gradientUnits="userSpaceOnUse"
                x1={branch.x1}
                y1={branch.y1}
                x2={branch.x2}
                y2={branch.y2}
              >
                <stop offset="0%" stopColor={branch.colorFrom} stopOpacity={0.9} />
                <stop offset="100%" stopColor={branch.colorTo} stopOpacity={0.9} />
              </linearGradient>
            ))}
          </defs>

          {displayLayouts.map((layout) => {
            const d = verticalStreamPath(
              layout.x,
              layout.yStart,
              layout.yEnd,
              canvasSize.h,
              canvasSize.w
            );
            const highlighted = hoveredMovementId === layout.movement.id;
            return (
              <path
                key={layout.movement.id}
                d={d}
                className={`vflow-stream${highlighted ? ' vflow-stream-highlighted' : ''}`}
                stroke={layout.displayColor}
                fill="none"
                style={{ strokeWidth: layout.strokePx }}
                onMouseEnter={() => setHoveredMovementId(layout.movement.id)}
                onMouseLeave={() => setHoveredMovementId(null)}
                onClick={(e) => {
                  e.stopPropagation();
                  onMovementClick?.(layout.movement.id);
                }}
              />
            );
          })}

          {displayBranches.map((branch) => {
            const highlighted =
              hoveredMovementId != null &&
              (branch.fromId === hoveredMovementId || branch.toId === hoveredMovementId);
            return (
              <path
                key={branch.key}
                d={branch.d}
                className={`vflow-branch${highlighted ? ' vflow-branch-highlighted' : ''}`}
                stroke={`url(#vflow-branch-grad-${branch.key})`}
                fill="none"
                style={{ strokeWidth: branch.strokePx }}
              />
            );
          })}
        </svg>

        {displayLayouts.map((layout) => {
          const midY = (layout.yStart + layout.yEnd) / 2;
          return (
            <button
              key={`label-${layout.movement.id}`}
              type="button"
              className="vflow-label"
              style={{
                left: `${layout.x}%`,
                bottom: `${midY}%`,
              }}
              onClick={(e) => {
                e.stopPropagation();
                onMovementClick?.(layout.movement.id);
              }}
              onMouseEnter={() => setHoveredMovementId(layout.movement.id)}
              onMouseLeave={() => setHoveredMovementId(null)}
            >
              {layout.movement.name}
            </button>
          );
        })}
      </div>
    </div>
  );
}
