// ─────────────────────────────────────────────────────────────
// STUDIO shared utilities — used across all tab views.
// ─────────────────────────────────────────────────────────────

function SectionHeading({ p, title, blurb }) {
  return (
    <div style={{ marginBottom: 16 }}>
      <h2 style={{
        fontSize: 28, fontWeight: 500, letterSpacing: '-0.025em',
        lineHeight: 1.05, color: p.ink, margin: 0,
        fontFamily: '"Geist", sans-serif',
      }}>{title}</h2>
      {blurb && (
        <div style={{
          marginTop: 4, fontSize: 12.5, color: p.inkDim, lineHeight: 1.5,
          maxWidth: 320,
        }}>{blurb}</div>
      )}
    </div>
  );
}

function StudioChip({ p, tone, label }) {
  const map = {
    ok:      { fg: p.greenOk, bg: 'rgba(126,196,138,0.10)', bd: 'rgba(126,196,138,0.30)' },
    err:     { fg: p.redErr,  bg: 'rgba(224,123,107,0.10)', bd: 'rgba(224,123,107,0.30)' },
    warn:    { fg: p.ochre,   bg: 'rgba(200,164,92,0.10)',  bd: 'rgba(200,164,92,0.30)' },
    neutral: { fg: p.inkDim,  bg: 'transparent',            bd: p.hairline },
  }[tone] || {};
  return (
    <span style={{
      display: 'inline-flex', alignItems: 'center',
      fontFamily: '"Geist Mono", monospace',
      fontSize: 9, fontWeight: 600, letterSpacing: '0.1em',
      color: map.fg, background: map.bg,
      border: `1px solid ${map.bd}`,
      padding: '3px 7px', borderRadius: 4,
    }}>{label}</span>
  );
}

function EmptyState({ p, headline, sub }) {
  return (
    <div style={{ padding: '60px 16px', textAlign: 'center', color: p.inkDim }}>
      <div style={{
        width: 36, height: 36, borderRadius: '50%',
        margin: '0 auto 14px',
        border: `1px dashed ${p.hairline}`,
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        color: p.inkFaint,
        fontFamily: '"Geist Mono", monospace',
      }}>—</div>
      <div style={{ fontSize: 14, fontWeight: 500, color: p.ink, marginBottom: 4 }}>{headline}</div>
      <div style={{ fontSize: 12, lineHeight: 1.5, maxWidth: 260, margin: '0 auto' }}>{sub}</div>
    </div>
  );
}

function PrimaryButton({ p, onClick, children, accent, secondary, disabled, size = 'md' }) {
  const h = size === 'sm' ? 32 : 38;
  return (
    <button onClick={onClick} disabled={disabled} style={{
      width: '100%', height: h,
      background: disabled ? p.surface2 : (secondary ? 'transparent' : (accent || p.btnBg)),
      color: disabled ? p.inkFaint : (secondary ? p.ink : (accent ? '#fff' : p.btnInk)),
      border: secondary ? `1px solid ${p.hairline}` : 'none',
      borderRadius: 9, fontSize: size === 'sm' ? 12 : 13, fontWeight: 600,
      fontFamily: 'inherit', letterSpacing: '-0.01em',
      cursor: disabled ? 'not-allowed' : 'pointer',
      opacity: disabled ? 0.7 : 1,
    }}>{children}</button>
  );
}

// Inline form field — label + control. Studio styling.
function Field({ p, label, hint, children, accent }) {
  return (
    <div style={{ marginBottom: 12 }}>
      <div style={{
        display: 'flex', alignItems: 'baseline', justifyContent: 'space-between',
        marginBottom: 4,
      }}>
        <label style={{
          fontSize: 10, fontWeight: 600, letterSpacing: '0.08em',
          textTransform: 'uppercase', color: accent || p.inkFaint,
        }}>{label}</label>
        {hint && (
          <span style={{
            fontSize: 10, color: p.inkFaint,
            fontFamily: '"Geist Mono", monospace',
            fontVariantNumeric: 'tabular-nums',
          }}>{hint}</span>
        )}
      </div>
      {children}
    </div>
  );
}

// Standard text input
function TextInput({ p, value, onChange, placeholder, multiline, rows = 1 }) {
  const baseStyle = {
    width: '100%',
    background: p.bg,
    border: `1px solid ${p.hairline}`,
    borderRadius: 7,
    padding: '8px 10px',
    color: p.ink,
    fontFamily: 'inherit',
    fontSize: 13, lineHeight: 1.4,
    outline: 'none',
    resize: multiline ? 'vertical' : 'none',
  };
  const onFocus = e => { e.target.style.borderColor = p.ochre; };
  const onBlur  = e => { e.target.style.borderColor = p.hairline; };
  if (multiline) {
    return <textarea data-no-swipe value={value} onChange={e => onChange(e.target.value)}
      placeholder={placeholder} rows={rows} onFocus={onFocus} onBlur={onBlur} style={baseStyle} />;
  }
  return <input data-no-swipe type="text" value={value} onChange={e => onChange(e.target.value)}
    placeholder={placeholder} onFocus={onFocus} onBlur={onBlur} style={baseStyle} />;
}

// ─────────────────────────────────────────────────────────────
// SWIPEABLE CARD — drag left/right to reveal an action.
// Past the threshold, releasing commits the action.
// Doesn't fight scroll (only claims the gesture when motion is
// dominantly horizontal) and ignores pointerdowns on interactive
// children (buttons, sliders, inputs, anything with [data-no-swipe]).
// ─────────────────────────────────────────────────────────────
function SwipeableCard({ p, swipeLeft, swipeRight, children }) {
  const [dx, setDx] = React.useState(0);
  const [isDragging, setDragging] = React.useState(false);
  const dragRef = React.useRef(false);
  const capturedRef = React.useRef(false);
  const startX = React.useRef(0);
  const startY = React.useRef(0);
  const elRef = React.useRef(null);

  const onPointerDown = (e) => {
    if (e.pointerType === 'mouse' && e.button !== 0) return;
    const t = e.target;
    if (t.closest && t.closest('button, input, select, textarea, a, video, [role="button"], [role="slider"], [data-no-swipe]')) return;
    dragRef.current = true;
    capturedRef.current = false;
    startX.current = e.clientX;
    startY.current = e.clientY;
    setDragging(true);
  };

  const onPointerMove = (e) => {
    if (!dragRef.current) return;
    const ddx = e.clientX - startX.current;
    const ddy = e.clientY - startY.current;
    if (!capturedRef.current) {
      if (Math.abs(ddx) > 8 && Math.abs(ddx) > Math.abs(ddy) * 1.2) {
        capturedRef.current = true;
        try { elRef.current && elRef.current.setPointerCapture(e.pointerId); } catch (_) {}
      } else if (Math.abs(ddy) > 8) {
        dragRef.current = false;
        setDragging(false);
        setDx(0);
        return;
      }
    }
    if (capturedRef.current) {
      let next = ddx;
      if (next < 0 && !swipeLeft)  next = next * 0.2;
      if (next > 0 && !swipeRight) next = next * 0.2;
      setDx(next);
    }
  };

  const onPointerUp = (e) => {
    if (!dragRef.current) return;
    dragRef.current = false;
    setDragging(false);
    if (!capturedRef.current) { setDx(0); return; }
    const threshold = 90;
    if (dx <= -threshold && swipeLeft) {
      setDx(-440);
      setTimeout(() => { swipeLeft.onCommit(); }, 200);
    } else if (dx >= threshold && swipeRight) {
      setDx(440);
      setTimeout(() => { swipeRight.onCommit(); }, 200);
    } else {
      setDx(0);
    }
  };

  const past = Math.abs(dx) >= 90;
  const isLeftSide = dx < 0;
  const action = isLeftSide ? swipeLeft : swipeRight;
  const actionBg = action?.color || (isLeftSide ? '#a04040' : p.greenOk);

  return (
    <div style={{ position: 'relative', borderRadius: 14, overflow: 'hidden' }}>
      {dx !== 0 && action && (
        <div aria-hidden style={{
          position: 'absolute', inset: 0,
          background: actionBg,
          display: 'flex', alignItems: 'center',
          justifyContent: isLeftSide ? 'flex-end' : 'flex-start',
          padding: '0 24px',
          color: '#fff', fontFamily: 'inherit',
          fontSize: 13, fontWeight: past ? 700 : 500,
          letterSpacing: past ? '0.02em' : '0',
          transition: 'opacity 80ms',
          opacity: Math.min(1, Math.abs(dx) / 60),
        }}>
          <span style={{
            display: 'inline-flex', alignItems: 'center', gap: 6,
            transform: `scale(${past ? 1.06 : 0.98})`,
            transition: 'transform 120ms',
          }}>
            {isLeftSide && action.icon && <span>{action.icon}</span>}
            <span>{past ? (action.releaseLabel || action.label) : action.label}</span>
            {!isLeftSide && action.icon && <span>{action.icon}</span>}
          </span>
        </div>
      )}

      <div ref={elRef}
        onPointerDown={onPointerDown}
        onPointerMove={onPointerMove}
        onPointerUp={onPointerUp}
        onPointerCancel={onPointerUp}
        style={{
          transform: `translateX(${dx}px)`,
          transition: isDragging ? 'none' : 'transform 0.24s cubic-bezier(.2,.7,.2,1)',
          touchAction: 'pan-y',
          willChange: 'transform',
          cursor: isDragging ? 'grabbing' : 'auto',
        }}>
        {children}
      </div>
    </div>
  );
}

// Striped svg keyframe placeholder — different hue per index
function KeyframePlaceholder({ seed = 0, label, brighter = false }) {
  const hue = (40 + seed * 47) % 360;
  const sL = brighter ? 42 : 32;
  const sR = brighter ? 28 : 18;
  return (
    <div style={{
      width: '100%', height: '100%',
      backgroundImage: `repeating-linear-gradient(135deg, rgba(255,255,255,0.05) 0 2px, transparent 2px 8px), linear-gradient(135deg, hsl(${hue} 32% ${sL}%) 0%, hsl(${hue} 36% ${sR}%) 100%)`,
      position: 'relative',
    }}>
      {label && (
        <span style={{
          position: 'absolute', bottom: 4, left: 5, right: 5,
          fontFamily: '"Geist Mono", monospace',
          fontSize: 8, color: 'rgba(255,255,255,0.78)',
          textShadow: '0 1px 2px rgba(0,0,0,0.6)',
          letterSpacing: '0.04em',
          whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
        }}>{label}</span>
      )}
    </div>
  );
}

function formatN(n) {
  if (n == null) return '0';
  if (n >= 1000) return (n / 1000).toFixed(n >= 10000 ? 0 : 1) + 'K';
  return String(n);
}

// ─────────────────────────────────────────────────────────────
// useScrollIsolation — attaches a NATIVE wheel listener at the
// scroll container that stopPropagations the event before it can
// bubble up to the design-canvas viewport. React's onWheel isn't
// enough: it fires after the canvas's native preventDefault has
// already killed the default scroll action.
// Returns a ref to spread onto the scrollable element.
// ─────────────────────────────────────────────────────────────
function useScrollIsolation() {
  const ref = React.useRef(null);
  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const stop = (e) => e.stopPropagation();
    // passive: false so listeners higher up can't override; but we
    // don't preventDefault, so native scroll proceeds.
    el.addEventListener('wheel', stop, { passive: false });
    return () => el.removeEventListener('wheel', stop);
  }, []);
  return ref;
}

// ─────────────────────────────────────────────────────────────
// usePhoneScroll — does everything useScrollIsolation does PLUS
// implements click-and-drag-to-scroll (desktop browsers don't do
// this natively; it's a touch-only behavior). Coordinates with
// SwipeableCard's horizontal swipe by waiting longer (12px) before
// claiming the gesture so short horizontal motions go to swipe.
// Ignores interactive elements and anything tagged [data-no-swipe].
// ─────────────────────────────────────────────────────────────
function usePhoneScroll() {
  const ref = React.useRef(null);
  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;

    // Wheel — stop bubbling to the canvas viewport so its zoom handler doesn't fire.
    const onWheel = (e) => e.stopPropagation();
    el.addEventListener('wheel', onWheel, { passive: false });

    // Click-drag-to-scroll (mouse / pen only — touch uses native scroll).
    let active = false;
    let claimed = false;
    let startY = 0, startX = 0, startScroll = 0, pid = null;

    const release = () => {
      active = false; claimed = false; pid = null;
      el.style.cursor = '';
      document.body.style.userSelect = '';
    };

    const onDown = (e) => {
      if (e.pointerType === 'touch') return;
      if (e.button !== 0) return;
      const t = e.target;
      if (t.closest && t.closest('button, input, select, textarea, a, video, [role="slider"], [data-no-swipe]')) return;
      active = true; claimed = false;
      startY = e.clientY; startX = e.clientX;
      startScroll = el.scrollTop;
      pid = e.pointerId;
    };

    const onMove = (e) => {
      if (!active || e.pointerId !== pid) return;
      const dy = e.clientY - startY;
      const dx = e.clientX - startX;
      if (!claimed) {
        // Wait longer than SwipeableCard's 8px so horizontal swipe wins ties.
        if (Math.abs(dy) > 12 && Math.abs(dy) > Math.abs(dx) * 1.3) {
          claimed = true;
          el.style.cursor = 'grabbing';
          document.body.style.userSelect = 'none';
        } else if (Math.abs(dx) > 12) {
          release();
          return;
        }
      }
      if (claimed) {
        el.scrollTop = startScroll - dy;
        e.preventDefault();
      }
    };

    const onUp = () => { if (active) release(); };

    el.addEventListener('pointerdown', onDown);
    document.addEventListener('pointermove', onMove, { passive: false });
    document.addEventListener('pointerup', onUp);
    document.addEventListener('pointercancel', onUp);

    return () => {
      el.removeEventListener('wheel', onWheel);
      el.removeEventListener('pointerdown', onDown);
      document.removeEventListener('pointermove', onMove);
      document.removeEventListener('pointerup', onUp);
      document.removeEventListener('pointercancel', onUp);
    };
  }, []);
  return ref;
}

// View switcher is in studio-v2.jsx. View components are in per-tab files.

Object.assign(window, {
  SectionHeading, StudioChip, EmptyState, PrimaryButton, Field, TextInput,
  SwipeableCard, KeyframePlaceholder, formatN, useScrollIsolation, usePhoneScroll,
});
