// ------------------------------------------------------------------------
//  useColumnPopup - owns the column-header popup AND the event-tag popup.
//
//  Both popups behave identically:
//    - click an anchor - toggle the popup open
//    - hover an anchor for N ms - open
//    - move into the popup - keep it open
//    - leave the popup - close after 200 ms
//    - click outside / scroll / resize - close immediately
//
//  Returns everything app.js needs to wire up the th cells and event-tag
//  spans, plus the popup state objects for rendering the floating panels.
// ------------------------------------------------------------------------

import { useState, useEffect, useRef, useCallback } from '../../vendor/preact-hooks.js';

export function useColumnPopup() {
  // --- Column header popup --------------------------------------------
  const [colPopup, setColPopup] = useState(null);
  const colPopupRef   = useRef(null);
  const colPopupThRef = useRef(null);
  const hoverTimerRef = useRef(null);
  const closeTimerRef = useRef(null);

  // --- Event tag popup ------------------------------------------------
  // Holds { events[], slideIndex, x, y, arrowLeft, below }
  // When multiple events are in the popup they auto-cycle with a crossfade.
  const [eventTagPopup, setEventTagPopup] = useState(null);
  const [evSlideIndex, setEvSlideIndex]   = useState(0);
  // 'entering' | 'exiting' | null - drives CSS crossfade classes
  const [evTransition, setEvTransition]   = useState(null);
  const prevSlideIndexRef = useRef(0);
  const eventTagPopupRef  = useRef(null);
  const evHoverTimerRef   = useRef(null);
  const evCloseTimerRef   = useRef(null);
  const evSlideTimerRef   = useRef(null);

  const calcPopupPos = (thEl) => {
    const rect = thEl.getBoundingClientRect();
    // Use a generous height estimate so tall (text-heavy) popups prefer going
    // below rather than overflowing the top. The post-render clamp effect
    // catches any remaining overflow after the real height is known.
    const popupW = 260, popupH = 220, margin = 8, gap = 6;
    let x = rect.left + rect.width / 2;
    x = Math.max(popupW / 2 + margin, Math.min(x, window.innerWidth - popupW / 2 - margin));
    const below = rect.top < popupH + gap + margin;
    const y = below ? rect.bottom + gap : rect.top - gap;
    const popupLeft = x - popupW / 2;
    const arrowLeft = Math.max(16, Math.min(rect.left + rect.width / 2 - popupLeft, popupW - 16));
    return { x, y, arrowLeft, below };
  };

  const openPopup = (key, thEl) => {
    colPopupThRef.current = thEl;
    setColPopup({ key, ...calcPopupPos(thEl) });
  };

  const closePopup = () => {
    setColPopup(null);
    colPopupThRef.current = null;
    clearTimeout(hoverTimerRef.current);
    clearTimeout(closeTimerRef.current);
  };

  const handleThClick = (key, e) => {
    clearTimeout(hoverTimerRef.current);
    clearTimeout(closeTimerRef.current);
    if (colPopup?.key === key) { closePopup(); return; }
    openPopup(key, e.currentTarget);
  };

  const handleThEnter = (key, e) => {
    clearTimeout(hoverTimerRef.current);
    clearTimeout(closeTimerRef.current);
    // If a different popup is open, close it immediately and start fresh timer
    if (colPopup && colPopup.key !== key) closePopup();
    if (colPopup?.key === key) return; // already showing this one
    const thEl = e.currentTarget;
    hoverTimerRef.current = setTimeout(() => openPopup(key, thEl), 1000);
  };

  // Leaving a th: just cancel the pending open. Don't auto-close -
  // the user might be moving into the popup, or just passing through.
  const handleThLeave = () => {
    clearTimeout(hoverTimerRef.current);
  };

  // Popup mouse handlers: keep it open while hovering, close on leave.
  const handlePopupEnter = () => clearTimeout(closeTimerRef.current);
  const handlePopupLeave = () => { closeTimerRef.current = setTimeout(closePopup, 200); };

  useEffect(() => {
    if (!colPopup) return;
    const onClickOutside = (e) => {
      if (colPopupRef.current && !colPopupRef.current.contains(e.target)) closePopup();
    };
    // Close on scroll (avoids scroll-linked jank) or resize
    const onScrollOrResize = () => closePopup();
    document.addEventListener('mousedown', onClickOutside);
    window.addEventListener('scroll', onScrollOrResize, { passive: true, capture: true });
    window.addEventListener('resize', onScrollOrResize, { passive: true });
    return () => {
      document.removeEventListener('mousedown', onClickOutside);
      window.removeEventListener('scroll', onScrollOrResize, { capture: true });
      window.removeEventListener('resize', onScrollOrResize);
    };
  }, [colPopup]);

  // Post-render clamp: after the popup actually paints, measure its real
  // bounding rect and nudge y if it overflows the viewport top or bottom.
  // The >1 guard prevents floating-point micro-loops.
  useEffect(() => {
    if (!colPopup || !colPopupRef.current) return;
    const rect = colPopupRef.current.getBoundingClientRect();
    const MARGIN = 8;
    if (!colPopup.below && rect.top < MARGIN) {
      const delta = MARGIN - rect.top;
      if (delta > 1) setColPopup(prev => prev ? { ...prev, y: prev.y + delta } : prev);
    } else if (colPopup.below && rect.bottom > window.innerHeight - MARGIN) {
      const delta = rect.bottom - (window.innerHeight - MARGIN);
      if (delta > 1) setColPopup(prev => prev ? { ...prev, y: prev.y - delta } : prev);
    }
  }, [colPopup]);

  // --- Event tag popup helpers -----------------------------------------
  const calcEventPopupPos = (spanEl) => {
    const rect = spanEl.getBoundingClientRect();
    const popupW = 260, popupH = 160, margin = 8, gap = 6;
    let x = rect.left + rect.width / 2;
    x = Math.max(popupW / 2 + margin, Math.min(x, window.innerWidth - popupW / 2 - margin));
    const below = rect.top < popupH + gap + margin;
    const y = below ? rect.bottom + gap : rect.top - gap;
    const popupLeft = x - popupW / 2;
    const arrowLeft = Math.max(16, Math.min(rect.left + rect.width / 2 - popupLeft, popupW - 16));
    return { x, y, arrowLeft, below };
  };

  // --- Slideshow advance -----------------------------------------------
  // evSlideTo triggers a crossfade to a new slide index.
  const evSlideTo = useCallback((nextIndex) => {
    setEvTransition('exiting');
    // After the exit animation (~400 ms) swap content and fade in
    setTimeout(() => {
      prevSlideIndexRef.current = nextIndex;
      setEvSlideIndex(nextIndex);
      setEvTransition('entering');
      // Clear the entering class once the animation finishes
      setTimeout(() => setEvTransition(null), 420);
    }, 400);
  }, []);

  // Auto-advance slideshow when popup is open with multiple events.
  // We store a ref to evSlideIndex so the interval closure always reads
  // the latest value without needing to be recreated on every slide change.
  const evSlideIndexRef = useRef(0);
  useEffect(() => { evSlideIndexRef.current = evSlideIndex; }, [evSlideIndex]);

  useEffect(() => {
    if (!eventTagPopup || eventTagPopup.events.length <= 1) {
      clearInterval(evSlideTimerRef.current);
      return;
    }
    evSlideTimerRef.current = setInterval(() => {
      const next = (evSlideIndexRef.current + 1) % eventTagPopup.events.length;
      evSlideTo(next);
    }, 4000);
    return () => clearInterval(evSlideTimerRef.current);
  }, [eventTagPopup, evSlideTo]);

  // --- Event tag popup handlers ----------------------------------------
  const openEventTagPopup = (events, spanEl, startIndex = 0) => {
    clearInterval(evSlideTimerRef.current);
    setEvSlideIndex(startIndex);
    prevSlideIndexRef.current = startIndex;
    setEvTransition(null);
    setEventTagPopup({ events, ...calcEventPopupPos(spanEl) });
  };

  const closeEventTagPopup = () => {
    setEventTagPopup(null);
    setEvSlideIndex(0);
    setEvTransition(null);
    clearTimeout(evHoverTimerRef.current);
    clearTimeout(evCloseTimerRef.current);
    clearInterval(evSlideTimerRef.current);
  };

  // rowEvents = all events for that row (passed in from app.js)
  const handleEventTagClick = (rowEvents, startIndex, e) => {
    e.stopPropagation();
    clearTimeout(evHoverTimerRef.current);
    clearTimeout(evCloseTimerRef.current);
    // Toggle off if same set already open
    if (eventTagPopup && JSON.stringify(eventTagPopup.events.map(ev => ev.id)) === JSON.stringify(rowEvents.map(ev => ev.id))) {
      closeEventTagPopup(); return;
    }
    openEventTagPopup(rowEvents, e.currentTarget, startIndex);
  };

  const handleEventTagEnter = (rowEvents, startIndex, e) => {
    clearTimeout(evHoverTimerRef.current);
    clearTimeout(evCloseTimerRef.current);
    const spanEl = e.currentTarget;
    evHoverTimerRef.current = setTimeout(() => openEventTagPopup(rowEvents, spanEl, startIndex), 700);
  };

  const handleEventTagLeave = () => clearTimeout(evHoverTimerRef.current);

  const handleEventTagPopupEnter = () => clearTimeout(evCloseTimerRef.current);
  const handleEventTagPopupLeave = () => { evCloseTimerRef.current = setTimeout(closeEventTagPopup, 200); };

  useEffect(() => {
    if (!eventTagPopup) return;
    const onClickOutside = (e) => {
      if (eventTagPopupRef.current && !eventTagPopupRef.current.contains(e.target)) closeEventTagPopup();
    };
    const onScrollOrResize = () => closeEventTagPopup();
    document.addEventListener('mousedown', onClickOutside);
    window.addEventListener('scroll', onScrollOrResize, { passive: true, capture: true });
    window.addEventListener('resize', onScrollOrResize, { passive: true });
    return () => {
      document.removeEventListener('mousedown', onClickOutside);
      window.removeEventListener('scroll', onScrollOrResize, { capture: true });
      window.removeEventListener('resize', onScrollOrResize);
    };
  }, [eventTagPopup]);

  // Post-render clamp for event-tag popup (same logic as col popup above).
  useEffect(() => {
    if (!eventTagPopup || !eventTagPopupRef.current) return;
    const rect = eventTagPopupRef.current.getBoundingClientRect();
    const MARGIN = 8;
    if (!eventTagPopup.below && rect.top < MARGIN) {
      const delta = MARGIN - rect.top;
      if (delta > 1) setEventTagPopup(prev => prev ? { ...prev, y: prev.y + delta } : prev);
    } else if (eventTagPopup.below && rect.bottom > window.innerHeight - MARGIN) {
      const delta = rect.bottom - (window.innerHeight - MARGIN);
      if (delta > 1) setEventTagPopup(prev => prev ? { ...prev, y: prev.y - delta } : prev);
    }
  }, [eventTagPopup]);

  return {
    // column-header popup
    colPopup, colPopupRef,
    handleThClick, handleThEnter, handleThLeave,
    handlePopupEnter, handlePopupLeave,
    closePopup,
    // event-tag popup
    eventTagPopup, eventTagPopupRef,
    evSlideIndex, evTransition, evSlideTo,
    handleEventTagClick, handleEventTagEnter, handleEventTagLeave,
    handleEventTagPopupEnter, handleEventTagPopupLeave,
    closeEventTagPopup,
  };
}
