/**
 * useToolbarOverflow - Detects which toolbar items overflow their container.
 *
 * Uses a ResizeObserver on the toolbar-right container to decide which items
 * should be collapsed into an overflow dropdown.  Children opt in via
 * `data-toolbar-id` and `data-toolbar-priority` attributes (lower priority
 * collapses first).
 *
 * Key design: items are never removed from the DOM — they are hidden via a
 * CSS class.  The hook remembers every item's "natural" width from its first
 * visible measurement so that hidden items still participate in the overflow
 * calculation.  This avoids the chicken-and-egg problem where you can't
 * measure something that isn't rendered.
 *
 * Compact mode: when the overall toolbar is very narrow, the normal
 * three-column layout becomes untenable even with all items collapsed.
 * An optional `toolbarRef` enables compact-mode detection.  When the
 * toolbar width drops below `COMPACT_THRESHOLD`, `compactMode` is set to
 * true and ALL data-toolbar-id items are forced into the overflow menu.
 * The Toolbar component then uses a two-row layout: center+right on
 * row 1, left region on row 2.
 */

import { useCallback, useEffect, useRef, useState } from 'react';

/** Minimum width (px) the overflow trigger button needs. */
const OVERFLOW_BUTTON_WIDTH = 42;

/** Gap between flex children in toolbar-right (matches CSS gap: 8px). */
const FLEX_GAP = 8;

/**
 * Toolbar width (px) below which compact mode activates.
 * At this width the three equal-width columns (~220px each) are too narrow
 * for meaningful content, so we switch to a two-row layout.
 */
const COMPACT_THRESHOLD = 660;

export interface OverflowState {
  /** Set of `data-toolbar-id` values that should be hidden inline and
   *  shown in the overflow menu instead. */
  overflowedIds: Set<string>;
  /** True when at least one item has been collapsed. */
  showOverflowMenu: boolean;
  /** True when the toolbar is so narrow it needs a two-row compact layout. */
  compactMode: boolean;
}

interface ItemMeta {
  id: string;
  priority: number;
  width: number;
}

export function useToolbarOverflow(
  containerRef: React.RefObject<HTMLElement | null>,
  toolbarRef?: React.RefObject<HTMLElement | null>,
): OverflowState {
  const [state, setState] = useState<OverflowState>({
    overflowedIds: new Set<string>(),
    showOverflowMenu: false,
    compactMode: false,
  });

  // Persistent map of id → natural width.  Once an item has been measured
  // while visible, we keep its width even after it's hidden.
  const widthCache = useRef<Map<string, number>>(new Map());

  const rafId = useRef<number>(0);

  const recalculate = useCallback(() => {
    const container = containerRef.current;
    if (!container) return;

    const availableWidth = container.clientWidth;

    // If the container hasn't been laid out yet (e.g. JSDOM), skip
    if (availableWidth === 0) {
      setState({ overflowedIds: new Set<string>(), showOverflowMenu: false, compactMode: false });
      return;
    }

    // Check compact mode: is the overall toolbar too narrow for 3 columns?
    const toolbarWidth = toolbarRef?.current?.clientWidth ?? 0;
    const compact = toolbarWidth > 0 && toolbarWidth < COMPACT_THRESHOLD;

    // Scan all direct children with data-toolbar-id.
    // Update the width cache for any item whose current width is > 0
    // (i.e. it's currently visible in the DOM, not hidden).
    const allIds: { id: string; priority: number }[] = [];
    const children = container.children;

    for (let i = 0; i < children.length; i++) {
      const child = children[i] as HTMLElement;
      const id = child.getAttribute('data-toolbar-id');
      if (!id) continue;

      const priority = Number(child.getAttribute('data-toolbar-priority') ?? '999');
      allIds.push({ id, priority });

      const rect = child.getBoundingClientRect();
      if (rect.width > 0) {
        widthCache.current.set(id, rect.width);
      }
    }

    if (allIds.length === 0) {
      setState({ overflowedIds: new Set<string>(), showOverflowMenu: false, compactMode: compact });
      return;
    }

    // In compact mode, force ALL items into the overflow menu.
    if (compact) {
      const allOverflowed = new Set<string>(allIds.map(item => item.id));
      setState({
        overflowedIds: allOverflowed,
        showOverflowMenu: true,
        compactMode: true,
      });
      return;
    }

    // Build the list of items with their cached widths for the calculation.
    // Items that have never been measured (no cached width) are assigned a
    // default width so they still participate in the overflow math.
    const DEFAULT_BUTTON_WIDTH = 38; // approximate icon-only button width
    const items: ItemMeta[] = allIds.map(({ id, priority }) => ({
      id,
      priority,
      width: widthCache.current.get(id) ?? DEFAULT_BUTTON_WIDTH,
    }));

    // Total width of all items + gaps between them
    const totalGaps = Math.max(0, items.length - 1) * FLEX_GAP;
    const totalContentWidth = items.reduce((sum, item) => sum + item.width, 0) + totalGaps;

    // If everything fits, no overflow needed
    if (totalContentWidth <= availableWidth) {
      setState({ overflowedIds: new Set<string>(), showOverflowMenu: false, compactMode: false });
      return;
    }

    // Sort by priority ascending — lowest-priority items collapse first.
    const sortedByPriority = [...items].sort((a, b) => a.priority - b.priority);

    // Reserve space for the overflow button itself.
    const targetWidth = availableWidth - OVERFLOW_BUTTON_WIDTH - FLEX_GAP;
    let currentWidth = totalContentWidth;
    const overflowedIds = new Set<string>();

    for (const item of sortedByPriority) {
      if (currentWidth <= targetWidth) break;
      currentWidth -= item.width + FLEX_GAP;
      overflowedIds.add(item.id);
    }

    setState({
      overflowedIds,
      showOverflowMenu: overflowedIds.size > 0,
      compactMode: false,
    });
  }, [containerRef, toolbarRef]);

  useEffect(() => {
    const container = containerRef.current;
    if (!container) return;

    const observer = new ResizeObserver(() => {
      // Debounce with rAF to avoid layout thrashing
      cancelAnimationFrame(rafId.current);
      rafId.current = requestAnimationFrame(recalculate);
    });

    observer.observe(container);

    // Also observe the toolbar container for compact mode detection.
    const toolbar = toolbarRef?.current;
    if (toolbar && toolbar !== container) {
      observer.observe(toolbar);
    }

    // Schedule the initial calculation after the browser has painted so
    // that all children have their layout dimensions available.
    rafId.current = requestAnimationFrame(recalculate);

    return () => {
      cancelAnimationFrame(rafId.current);
      observer.disconnect();
    };
  }, [containerRef, toolbarRef, recalculate]);

  return state;
}
