// TimelineStrip — horizontally swipe-able grid of cells.
// Pinned left column (LeftSlot), scrollable track on the right with snap-by-cell.
// Cell rendering is delegated to props.renderCell so we can use this for both
// expanded (rows of check circles) and collapsed (single row of pies) views,
// and across all four cadence kinds (daily / set-days / n-per-week / weekly).
//
// Scroll position: a ref so we can preserve it when the view re-renders,
// and so collapsed/expanded modes can stay in sync.

const VISIBLE_CELLS = 6;      // days shown at once (tight packing); columns fit the width exactly so nothing clips and snap is clean
const STRIP_PAD_X = 18;       // matches ROW_PAD_X used elsewhere
const HEADER_H = 24;          // header row height (date labels) — just enough for the month flag ("AUG") without pushing the pies down
const HEADER_GAP = 5;         // gap between the date labels and the circles (smaller than the inter-row gap, so circles sit higher)

// Shared scroll position (px from RIGHT, since we snap to today on the right).
// Held in module scope so toggling collapsed/expanded preserves it.
const __stripScrollState = { fromRight: 0 };

function TimelineStrip({
  cells,         // array of { key, date?, weekStart?, label, isToday, isFuture, monthFlag? } — newest LAST
  rows,          // [{ id, leftSlot: ReactNode, renderCell: (cell, ctx) => ReactNode, height }]
  showHeader = true,
  rowGap = 10,
  cellMaxWidth = 38,
  scrollKey,     // change to reset scroll (e.g. cadence change)
  selectedDay,   // underline the selected day's header
}) {
  const trackRef = React.useRef(null);
  const outerRef = React.useRef(null);
  const [cellW, setCellW] = React.useState(52);

  // Split the strip into equal columns: the pinned left column + VISIBLE_CELLS
  // date cells all get the SAME width, and they fill the width exactly — so the
  // circles pack tightly, spacing is uniform, and nothing clips (snap is clean).
  React.useEffect(() => {
    const el = outerRef.current;
    if (!el) return;
    const ro = new ResizeObserver(() => {
      const content = el.clientWidth - STRIP_PAD_X * 2;
      const cw = content / (VISIBLE_CELLS + 1);
      if (cw > 0) setCellW(cw);
    });
    ro.observe(el);
    return () => ro.disconnect();
  }, []);

  // Pointer-drag-to-scroll (so the strip is draggable with mouse, not just touch)
  React.useEffect(() => {
    const el = trackRef.current;
    if (!el) return;
    let isDown = false, startX = 0, startScroll = 0, moved = false;
    const onDown = (e) => {
      isDown = true; moved = false;
      startX = e.clientX;
      startScroll = el.scrollLeft;
      // NOTE: do NOT setPointerCapture here — capturing on every touch steals the
      // tap from the day cells (their click never fires). We only capture once a
      // real drag starts (see onMove), so plain taps reach the pie buttons.
    };
    const onMove = (e) => {
      if (!isDown) return;
      const dx = e.clientX - startX;
      if (Math.abs(dx) > 3 && !moved) { moved = true; el.setPointerCapture?.(e.pointerId); }
      if (moved) el.scrollLeft = startScroll - dx;
    };
    const onUp = (e) => {
      if (!isDown) return;
      isDown = false;
      if (moved) {
        try { el.releasePointerCapture?.(e.pointerId); } catch (_) {}
        // suppress the click that follows a drag so it doesn't select a day
        const blocker = (ev) => { ev.stopPropagation(); ev.preventDefault(); };
        el.addEventListener('click', blocker, { capture: true, once: true });
      }
    };
    el.addEventListener('pointerdown', onDown);
    el.addEventListener('pointermove', onMove);
    el.addEventListener('pointerup', onUp);
    el.addEventListener('pointercancel', onUp);
    return () => {
      el.removeEventListener('pointerdown', onDown);
      el.removeEventListener('pointermove', onMove);
      el.removeEventListener('pointerup', onUp);
      el.removeEventListener('pointercancel', onUp);
    };
  }, []);

  // After layout, jump scroll so that the LAST cell (today) is rightmost.
  // Restore preserved scroll position on subsequent renders.
  const lastScrollKey = React.useRef(scrollKey);
  React.useLayoutEffect(() => {
    const el = trackRef.current;
    if (!el) return;
    if (lastScrollKey.current !== scrollKey) {
      // cadence changed: snap to today (rightmost)
      el.scrollLeft = el.scrollWidth - el.clientWidth;
      __stripScrollState.fromRight = 0;
      lastScrollKey.current = scrollKey;
      return;
    }
    // restore preserved position
    const max = el.scrollWidth - el.clientWidth;
    el.scrollLeft = Math.max(0, max - __stripScrollState.fromRight);
  }, [scrollKey, cells.length, cellW]);

  // Track scroll offset so we can preserve it across mode toggles
  const onScroll = () => {
    const el = trackRef.current;
    if (!el) return;
    const max = el.scrollWidth - el.clientWidth;
    __stripScrollState.fromRight = Math.max(0, max - el.scrollLeft);
  };

  // Snap to nearest cell on scroll-end
  const snapTimer = React.useRef(null);
  const onScrollEnd = () => {
    const el = trackRef.current;
    if (!el) return;
    clearTimeout(snapTimer.current);
    snapTimer.current = setTimeout(() => {
      const slot = cellW;
      const target = Math.round(el.scrollLeft / slot) * slot;
      if (Math.abs(target - el.scrollLeft) > 0.5) {
        el.scrollTo({ left: target, behavior: 'smooth' });
      }
    }, 80);
  };

  // total track inner width — cells laid out in a flex row
  const trackInnerWidth = cells.length * cellW;

  return (
    <div ref={outerRef} style={{
      display: 'grid',
      gridTemplateColumns: `${cellW}px 1fr`,
      gap: 0,
      padding: `8px ${STRIP_PAD_X}px 0`,
      position: 'relative',
    }}>
      {/* Pinned left column */}
      <div style={{
        display: 'flex', flexDirection: 'column', alignItems: 'center',
        paddingTop: showHeader ? HEADER_H + HEADER_GAP : 0,
        gap: rowGap,
      }}>
        {rows.map((r) => (
          <div key={r.id} style={{ height: r.height || cellMaxWidth, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
            {r.leftSlot}
          </div>
        ))}
      </div>

      {/* Scrollable track with snap */}
      <div
        ref={trackRef}
        onScroll={(e) => { onScroll(); onScrollEnd(); }}
        style={{
          overflowX: 'auto',
          overflowY: 'hidden',
          scrollSnapType: 'x mandatory',
          // hide scrollbars
          scrollbarWidth: 'none',
          msOverflowStyle: 'none',
          WebkitOverflowScrolling: 'touch',
          paddingLeft: 0,
        }}
        className="strip-track"
      >
        <div style={{ width: trackInnerWidth, minWidth: '100%' }}>
          {/* header row */}
          {showHeader && (
            <div style={{ display: 'flex', height: HEADER_H, marginBottom: HEADER_GAP, position: 'relative' }}>
              {cells.map((c) => (
                <CellHeader key={c.key} cell={c} width={cellW} selectedDay={selectedDay} />
              ))}
            </div>
          )}
          {/* member/data rows */}
          {rows.map((r) => (
            <div key={r.id} style={{
              display: 'flex',
              height: r.height || cellMaxWidth,
              marginBottom: rowGap,
              alignItems: 'center',
            }}>
              {cells.map((c, idx) => (
                <div key={c.key} data-testid={c.isToday ? 'cell-today' : (c.meta && c.meta.dateStr ? 'cell-' + c.meta.dateStr : undefined)} style={{
                  width: cellW, flexShrink: 0,
                  scrollSnapAlign: 'end',
                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                  position: 'relative',
                }}>
                  {r.renderCell(c, { cellW, prevCell: cells[idx - 1], nextCell: cells[idx + 1] })}
                </div>
              ))}
            </div>
          ))}
        </div>
      </div>

      <style>{`.strip-track::-webkit-scrollbar { display: none; }`}</style>
    </div>
  );
}

function CellHeader({ cell, width, selectedDay }) {
  // Renders the column header. Variants:
  //   - daily: date number, with month label flag if monthFlag is set
  //   - set-days: letter on top, date number below
  //   - weekly: "W##"
  //   - n-per-week: weekN as date — already merged by cell builder
  const isToday = cell.isToday;
  const isSelected = selectedDay && cell.meta && cell.meta.dateStr === selectedDay;
  const fadeFuture = cell.isFuture;
  return (
    <div style={{
      width, flexShrink: 0,
      display: 'flex', flexDirection: 'column', alignItems: 'center',
      justifyContent: 'flex-end',
      fontSize: 11, fontWeight: 600,
      color: GREEN,
      opacity: fadeFuture ? 0.4 : 1,
      lineHeight: 1.1,
      position: 'relative',
    }}>
      {cell.monthFlag && (
        <div style={{
          fontSize: 9, fontWeight: 700, letterSpacing: 0.6,
          color: INK_SOFT, textTransform: 'uppercase',
          marginBottom: 1,
        }}>
          {cell.monthFlag}
        </div>
      )}
      {cell.subLabel && (
        <div style={{ fontSize: 10, fontWeight: 600, color: INK_SOFT, marginBottom: 1 }}>
          {cell.subLabel}
        </div>
      )}
      <div style={{
        textDecoration: (isToday || isSelected) ? 'underline' : 'none',
        textDecorationThickness: isSelected ? 2 : 'auto',
        textUnderlineOffset: 3,
        fontWeight: isSelected ? 800 : 600,
        fontSize: cell.smallLabel ? 11 : 13,
      }}>
        {cell.label}
      </div>
    </div>
  );
}

Object.assign(window, { TimelineStrip, STRIP_PAD_X });
