// app-v2-parts.jsx — Lista v2 row-level components: squircle check, stepper,
// item row (swipe + haptic-feel animations), dropdown menu, undo snackbar.
// Exposes window.V2 = { setCtx, Circle2, Stepper2, Row2, Menu2, MenuItem2, Snackbar }.
(function () {
const { useState, useRef } = React;
const Ico = window.Ico;

// render context (unit system + locale) set by the app each render
let UNIT_SYSTEM = 'metric', LOCALE = 'en';
const setCtx = (c) => { UNIT_SYSTEM = c.unitSystem || 'metric'; LOCALE = c.locale || 'en'; };
const UNIT_MAP = { metric: { lb: 'kg', oz: 'g', cup: 'dl' }, imperial: { kg: 'lb', g: 'oz' } };
const dispUnit = (u) => {
  if (!u) return u;
  const conv = (UNIT_MAP[UNIT_SYSTEM] && UNIT_MAP[UNIT_SYSTEM][u]) || u;
  const L = window.I18N_T && window.I18N_T(LOCALE);
  return L ? L.unit(conv) : conv;
};
const fx = (t) => (t.effects && t.effects.blur && t.effects.blur !== 'none')
  ? { backdropFilter: t.effects.blur, WebkitBackdropFilter: t.effects.blur } : {};
const press = (scale = 0.88) => ({
  onPointerDown: (e) => (e.currentTarget.style.transform = `scale(${scale})`),
  onPointerUp: (e) => (e.currentTarget.style.transform = ''),
  onPointerLeave: (e) => (e.currentTarget.style.transform = ''),
});
const memberColor = (name) => { const m = window.LIST_MEMBERS || {}; const hit = Object.values(m).find((x) => x.name === name); return hit ? hit.color : '#8a93a3'; };

// ── squircle check ──────────────────────────────────────────────────
function Circle2({ t, checked, onClick }) {
  const sz = t.space.check;
  return (
    <button onClick={onClick} aria-label={checked ? 'uncheck' : 'check'} {...press(0.82)}
      style={{
        flex: '0 0 auto', width: sz, height: sz, borderRadius: t.radius.check,
        border: checked ? 'none' : `2px solid ${t.color.checkRing}`,
        background: checked ? t.accentGrad : 'transparent',
        boxShadow: checked ? `0 3px 10px ${t.accentGlow}` : 'none',
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        cursor: 'pointer', padding: 0, transition: 'background .15s, border-color .15s, box-shadow .2s, transform .12s',
      }}>
      {checked && <span style={{ display: 'inline-flex', animation: 'l2-check .32s cubic-bezier(.3,1.4,.4,1)' }}>{Ico.check(t.color.accentText, sz * 0.58)}</span>}
    </button>
  );
}

// ── qty stepper ─────────────────────────────────────────────────────
function Stepper2({ t, item, onStep }) {
  const btn = (dir, dis) => (
    <button disabled={dis} onClick={(e) => { e.stopPropagation(); onStep(dir); }} {...(dis ? {} : press(0.78))}
      style={{
        width: 26, height: 26, borderRadius: Math.max(5, t.radius.stepper - 4), border: 'none',
        background: 'transparent', cursor: dis ? 'default' : 'pointer', padding: 0,
        display: 'flex', alignItems: 'center', justifyContent: 'center', opacity: dis ? 0.3 : 1, transition: 'transform .1s',
      }}>
      {dir > 0 ? Ico.plus(t.color.textMuted, 13) : Ico.minus(t.color.textMuted, 13)}
    </button>
  );
  return (
    <div style={{ flex: '0 0 auto', display: 'flex', alignItems: 'center', background: t.color.surfaceAlt, border: `1px solid ${t.color.divider}`, borderRadius: t.radius.stepper, padding: '2px 3px', gap: 1 }}>
      {btn(-1, item.qty <= 1)}
      <div style={{ minWidth: 30, textAlign: 'center', fontSize: 13, fontWeight: 600, color: t.color.text, fontVariantNumeric: 'tabular-nums', lineHeight: 1, display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
        <span key={item.qty} style={{ display: 'inline-block', animation: 'l2-qty .22s cubic-bezier(.3,1.3,.4,1)' }}>{item.qty}</span>
        {item.unit ? <span style={{ fontSize: 9, fontWeight: 600, color: t.color.textFaint, marginTop: 1, letterSpacing: '.02em' }}>{dispUnit(item.unit)}</span> : null}
      </div>
      {btn(1, false)}
    </div>
  );
}

// ── one item row (swipe right = check, left = delete) ───────────────
function Row2({ t, list, item, showThumbs, swipe, flash, entering, surfaceBg, grip, onGripDown, sharing, onToggle, onStep, onRemove, onEditEmoji, onOpenDetail }) {
  const L = window.I18N_T ? window.I18N_T(LOCALE) : null;
  const trItem = (n) => (L ? L.item(n) : n);
  const [hover, setHover] = useState(false);
  const [dx, setDx] = useState(0);
  const [dragging, setDragging] = useState(false);
  const dxRef = useRef(0);
  const st = useRef({ x: 0, y: 0, active: false, decided: false, horiz: false, blockTap: false });
  const THRESH = 70;

  const onDown = (e) => {
    if (!swipe) return;
    st.current = { x: e.clientX, y: e.clientY, active: true, decided: false, horiz: false, blockTap: false };
  };
  const onMove = (e) => {
    const s = st.current; if (!s.active) return;
    const dX = e.clientX - s.x, dY = e.clientY - s.y;
    if (!s.decided && (Math.abs(dX) > 8 || Math.abs(dY) > 8)) {
      s.decided = true; s.horiz = Math.abs(dX) > Math.abs(dY);
      if (s.horiz) { setDragging(true); try { e.currentTarget.setPointerCapture(e.pointerId); } catch (_) {} }
    }
    if (s.decided && s.horiz) { const v = Math.max(-150, Math.min(150, dX)); dxRef.current = v; setDx(v); }
  };
  const finish = () => {
    const s = st.current; if (!s.active) return;
    const d = dxRef.current; s.active = false; setDragging(false);
    if (s.horiz) {
      s.blockTap = Math.abs(d) > 8;
      if (d > THRESH) { dxRef.current = 0; setDx(0); onToggle(item.id); return; }
      if (d < -THRESH) { dxRef.current = 0; setDx(0); onRemove(item.id); return; }
    }
    dxRef.current = 0; setDx(0);
  };

  const tileSz = 36;
  const rowContent = (
    <React.Fragment>
      {grip && (
        <button aria-label="reorder" title="Drag to reorder" onPointerDown={(e) => onGripDown && onGripDown(e, item.id)}
          style={{ flex: '0 0 auto', width: 20, height: 30, marginInlineStart: -4, marginInlineEnd: -6, border: 'none', background: 'transparent', cursor: 'grab', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 0, touchAction: 'none', opacity: hover ? 1 : 0.55, transition: 'opacity .15s' }}>
          {Ico.grip(t.color.textFaint, 15)}
        </button>
      )}
      <Circle2 t={t} checked={item.checked} onClick={() => onToggle(item.id)} />
      {showThumbs && (
        <button onClick={() => onEditEmoji(item.id)} aria-label="change icon" title="Tap to change icon" {...press(0.86)}
          style={{
            flex: '0 0 auto', width: tileSz, height: tileSz, borderRadius: t.radius.chip,
            background: item.checked ? t.color.surfaceAlt : `linear-gradient(150deg, ${t.color.surfaceAlt} 0%, ${t.color.accentSoft} 130%)`,
            display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 19, lineHeight: 1, padding: 0,
            border: `1px solid ${hover ? t.color.border : 'transparent'}`, cursor: 'pointer',
            opacity: item.checked ? 0.45 : 1, transition: 'opacity .15s, border-color .15s, transform .12s',
          }}>
          <span style={{ filter: item.checked ? 'grayscale(1)' : 'none' }}>
            {item.emoji ? item.emoji : <span style={{ fontFamily: t.font.head, fontSize: 14, fontWeight: 700, color: t.color.textFaint }}>{(trItem(item.name).trim()[0] || '?').toUpperCase()}</span>}
          </span>
        </button>
      )}
      <button onClick={() => { if (!st.current.blockTap) onOpenDetail(item.id); }} title="Edit item"
        style={{ flex: '1 1 auto', minWidth: 0, border: 'none', background: 'transparent', cursor: 'pointer', padding: 0, textAlign: 'start' }}>
        <div style={{
          fontSize: 15.5, fontWeight: 500, lineHeight: 1.25,
          color: item.checked ? t.color.textFaint : t.color.text,
          textDecoration: item.checked ? 'line-through' : 'none', textDecorationColor: t.color.textFaint, textWrap: 'pretty',
          transition: 'color .2s',
        }}>{trItem(item.name)}</div>
        {item.note ? (
          <div style={{ fontSize: 12.5, color: t.color.textFaint, marginTop: 2, lineHeight: 1.2, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{item.note}</div>
        ) : null}
        {sharing && item.by ? (
          <div style={{ display: 'flex', alignItems: 'center', gap: 5, marginTop: 3 }}>
            <span style={{ flex: '0 0 auto', width: 13, height: 13, borderRadius: '50%', background: memberColor(item.by), display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontSize: 8, fontWeight: 700, color: '#fff' }}>{item.by[0]}</span>
            <span style={{ fontSize: 11.5, color: t.color.textFaint }}>{item.by}</span>
          </div>
        ) : null}
      </button>
      {list.showQty && !item.checked && (<Stepper2 t={t} item={item} onStep={(d) => onStep(item.id, d)} />)}
      {list.showQty && item.checked && (item.qty > 1 || item.unit) && (
        <span style={{ flex: '0 0 auto', fontSize: 12, fontWeight: 600, color: t.color.textFaint, fontVariantNumeric: 'tabular-nums' }}>{item.qty}{item.unit ? ' ' + dispUnit(item.unit) : ''}</span>
      )}
      <button onClick={() => onRemove(item.id)} aria-label="remove"
        style={{
          flex: '0 0 auto', width: 26, height: 26, borderRadius: t.radius.pill, border: 'none', background: 'transparent',
          cursor: 'pointer', padding: 0, display: 'flex', alignItems: 'center', justifyContent: 'center',
          opacity: hover ? 0.8 : 0, transition: 'opacity .12s',
        }}>
        {Ico.x(t.color.textMuted, 12)}
      </button>
    </React.Fragment>
  );

  const fgStyle = {
    display: 'flex', alignItems: 'center', gap: 11,
    padding: `${t.space.rowPadY}px ${t.space.rowPadX}px`,
    background: flash ? t.color.accentSoft : ((swipe && (dragging || dx !== 0)) ? surfaceBg : 'transparent'),
    transform: swipe ? `translateX(${dx}px)` : 'none',
    transition: dragging ? 'background .4s' : 'transform .22s cubic-bezier(.3,.8,.3,1), background .4s',
    position: 'relative', zIndex: 1, touchAction: swipe ? 'pan-y' : 'auto',
  };
  const wrapStyle = { borderRadius: t.radius.row, overflow: 'hidden', animation: entering ? 'l2-enter .32s cubic-bezier(.3,1.1,.4,1)' : 'none' };

  if (!swipe) {
    return (
      <div onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)} style={wrapStyle}>
        <div style={fgStyle}>{rowContent}</div>
      </div>
    );
  }

  const revealRight = dx > 0;
  return (
    <div onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)} style={{ ...wrapStyle, position: 'relative' }}>
      {Math.abs(dx) > 4 && (
        <div style={{
          position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', direction: 'ltr',
          justifyContent: revealRight ? 'flex-start' : 'flex-end', padding: '0 20px', gap: 8,
          background: revealRight ? t.accentGrad : t.color.danger,
          color: revealRight ? t.color.accentText : '#fff', fontFamily: t.font.head, fontSize: 13.5, fontWeight: 700,
        }}>
          {revealRight ? Ico.check(t.color.accentText, 16) : null}
          <span>{revealRight ? (item.checked ? (L ? L.ui('uncheck') : 'Uncheck') : (L ? L.checked(list.checkedLabel) : list.checkedLabel)) : (L ? L.ui('delete') : 'Delete')}</span>
          {revealRight ? null : Ico.trash('#fff', 16)}
        </div>
      )}
      <div onPointerDown={onDown} onPointerMove={onMove} onPointerUp={finish} onPointerCancel={finish} style={fgStyle}>
        {rowContent}
      </div>
    </div>
  );
}

// ── dropdown menu ───────────────────────────────────────────────────
function Menu2({ t, open, onClose, children, title }) {
  if (!open) return null;
  return (
    <React.Fragment>
      <div onClick={onClose} style={{ position: 'fixed', inset: 0, zIndex: 40 }} />
      <div style={{
        position: 'absolute', top: 'calc(100% + 6px)', insetInlineEnd: 0, zIndex: 41, minWidth: 190,
        background: t.color.surface, ...fx(t), borderRadius: Math.max(13, t.radius.chip + 3),
        border: `1px solid ${t.color.border}`, boxShadow: t.shadow.pop, padding: 5,
        transformOrigin: 'top', animation: 'l2-menu .18s cubic-bezier(.3,1.2,.4,1)',
      }}>
        {title && <div style={{ fontFamily: t.font.head, fontSize: 10.5, fontWeight: 700, letterSpacing: '.1em', textTransform: 'uppercase', color: t.color.textFaint, padding: '6px 9px 5px' }}>{title}</div>}
        {children}
      </div>
    </React.Fragment>
  );
}
function MenuItem2({ t, label, icon, selected, danger, onClick }) {
  return (
    <button onClick={onClick} style={{
      display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10, width: '100%',
      padding: '9px 9px', borderRadius: Math.max(8, t.radius.chip - 1), border: 'none', cursor: 'pointer',
      background: selected ? t.color.accentSoft : 'transparent', fontFamily: t.font.body, fontSize: 13.5,
      fontWeight: selected ? 600 : 500, color: danger ? t.color.danger : (selected ? t.color.accent : t.color.text), textAlign: 'start',
    }}>
      <span style={{ display: 'flex', alignItems: 'center', gap: 9 }}>{icon}{label}</span>
      {selected && Ico.check(t.color.accent, 13)}
    </button>
  );
}

// ── undo snackbar ───────────────────────────────────────────────────
function Snackbar({ t, snack, onUndo, onDismiss }) {
  if (!snack) return null;
  return (
    <div style={{
      position: 'absolute', insetInlineStart: 16, insetInlineEnd: 16, bottom: 96, zIndex: 75,
      display: 'flex', justifyContent: 'center', pointerEvents: 'none',
    }}>
      <div style={{
        display: 'flex', alignItems: 'center', gap: 6, maxWidth: 340, width: '100%', pointerEvents: 'auto',
        background: t.mode === 'dark' ? 'rgba(16,21,32,0.88)' : 'rgba(28,36,52,0.92)', ...fx(t),
        border: '1px solid rgba(255,255,255,0.12)', borderRadius: Math.max(14, t.radius.chip + 4),
        boxShadow: t.shadow.pop, padding: '10px 8px 10px 15px',
        animation: 'l2-snack .26s cubic-bezier(.3,1.2,.4,1)',
      }}>
        <span style={{ flex: '1 1 auto', minWidth: 0, fontSize: 13.5, fontWeight: 500, color: 'rgba(255,255,255,.92)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{snack.label}</span>
        <button onClick={onUndo} {...press(0.92)} style={{
          flex: '0 0 auto', border: 'none', cursor: 'pointer', background: 'rgba(255,255,255,0.12)',
          color: '#fff', fontFamily: t.font.head, fontSize: 12.5, fontWeight: 700,
          padding: '7px 13px', borderRadius: t.radius.pill, transition: 'transform .1s',
        }}>{snack.undoLabel}</button>
        <button onClick={onDismiss} aria-label="dismiss" style={{ flex: '0 0 auto', width: 28, height: 28, border: 'none', background: 'transparent', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 0 }}>
          {Ico.x('rgba(255,255,255,.6)', 11)}
        </button>
      </div>
    </div>
  );
}

// ── abstract backdrop (premium glass) ───────────────────────────────
// Soft drifting orbs painted between the wallpaper and the content.
// mode: 'dynamic' (slow drift) | 'static' | 'off'
function Backdrop({ t, mode }) {
  if (!mode || mode === 'off') return null;
  const anim = mode === 'dynamic';
  // dark mode: glass-ribbon artwork, hue-shifted to the accent; light: soft orbs
  if (t.backdropImage) {
    return (
      <div aria-hidden="true" style={{ position: 'absolute', inset: 0, zIndex: -1, overflow: 'hidden', pointerEvents: 'none' }}>
        <div style={{
          position: 'absolute', inset: '-8%',
          backgroundImage: `url("${t.backdropImage}")`, backgroundSize: 'cover', backgroundPosition: 'center',
          filter: `hue-rotate(${t.backdropHue || 0}deg) saturate(1.05)`,
          transformOrigin: '58% 42%', willChange: anim ? 'transform' : 'auto',
          animation: anim ? 'l2-bgdrift 34s ease-in-out infinite alternate' : 'none',
        }} />
      </div>
    );
  }
  if (!t.orbs) return null;
  const spec = [
    { c: t.orbs[0], w: 460, left: '-28%', top: '-10%', d: 13 },
    { c: t.orbs[1], w: 400, right: '-26%', top: '14%', d: 17 },
    { c: t.orbs[2], w: 440, left: '2%', bottom: '-18%', d: 15 },
  ];
  return (
    <div aria-hidden="true" style={{ position: 'absolute', inset: 0, zIndex: -1, overflow: 'hidden', pointerEvents: 'none' }}>
      {spec.map((o, i) => o.c ? (
        <div key={i} style={{
          position: 'absolute', width: o.w, height: o.w, left: o.left, right: o.right, top: o.top, bottom: o.bottom,
          borderRadius: '50%', background: `radial-gradient(circle at 38% 36%, ${o.c} 0%, transparent 70%)`,
          filter: 'blur(42px)', willChange: anim ? 'transform' : 'auto',
          animation: anim ? `l2-drift${i + 1} ${o.d}s ease-in-out infinite alternate` : 'none',
        }} />
      ) : null)}
    </div>
  );
}

window.V2 = { setCtx, Circle2, Stepper2, Row2, Menu2, MenuItem2, Snackbar, Backdrop, fx, press };
})();
