/* =============================================================================
   SheetHead — shared UI primitives (cards, buttons, icons, modal)
   Presentation only. Exposes window.SH.ui.
============================================================================= */
(function(){
  const { useEffect, useRef } = React;

  const SUIT_GLYPH = { C: '♣', D: '♦', H: '♥', S: '♠' };
  const SUIT_RED = new Set(['D', 'H']);

  function cx(...xs) { return xs.filter(Boolean).join(' '); }

  // ---------- Icons (inline SVG) ----------
  function Icon({ name, size = 20 }) {
    const ss = { width: size, height: size };
    const stroke = { viewBox: '0 0 24 24', style: ss, fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': true };
    switch (name) {
      case 'play': return <svg {...stroke} strokeWidth="2.2"><polygon points="5 3 19 12 5 21 5 3"/></svg>;
      case 'users': return <svg {...stroke}><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>;
      case 'globe': return <svg {...stroke}><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15 15 0 0 1 4 10 15 15 0 0 1-4 10 15 15 0 0 1-4-10 15 15 0 0 1 4-10z"/></svg>;
      case 'bot': return <svg {...stroke}><rect x="3" y="11" width="18" height="10" rx="2"/><circle cx="12" cy="5" r="2"/><path d="M12 7v4"/><line x1="8" y1="16" x2="8" y2="16"/><line x1="16" y1="16" x2="16" y2="16"/></svg>;
      case 'settings': return <svg {...stroke}><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.6 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>;
      case 'book': return <svg {...stroke}><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>;
      case 'back': return <svg {...stroke} strokeWidth="2.2"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>;
      case 'trophy': return <svg {...stroke}><path d="M6 9H4.5a2.5 2.5 0 0 1 0-5H6"/><path d="M18 9h1.5a2.5 2.5 0 0 0 0-5H18"/><path d="M4 22h16"/><path d="M10 14.66V17c0 .55-.47.98-.97 1.21C7.85 18.75 7 20.24 7 22"/><path d="M14 14.66V17c0 .55.47.98.97 1.21C16.15 18.75 17 20.24 17 22"/><path d="M18 2H6v7a6 6 0 0 0 12 0V2Z"/></svg>;
      case 'user': return <svg {...stroke}><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>;
      case 'flame': return <svg viewBox="0 0 24 24" style={ss} fill="currentColor" aria-hidden="true"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/></svg>;
      case 'bolt': return <svg viewBox="0 0 24 24" style={ss} fill="currentColor" aria-hidden="true"><path d="M13 2 4 14h7l-1 8 9-12h-7z"/></svg>;
      case 'cw': return <svg {...stroke}><path d="M21 12a9 9 0 1 1-3-6.7"/><polyline points="21 3 21 9 15 9"/></svg>;
      case 'draw': return <svg {...stroke}><rect x="6" y="3" width="12" height="18" rx="2"/><path d="M12 8v8M9 13l3 3 3-3"/></svg>;
      case 'check': return <svg {...stroke} strokeWidth="3"><polyline points="4 12 10 18 20 6"/></svg>;
      case 'x': return <svg {...stroke} strokeWidth="3"><line x1="6" y1="6" x2="18" y2="18"/><line x1="18" y1="6" x2="6" y2="18"/></svg>;
      default: return null;
    }
  }


  // Face crop of the Joker artwork (used at icon size, e.g. in the effect ribbon).
  function JokerFace({ className }) { return <span className={cx('joker-face', className)} aria-hidden="true" />; }

  // Framer Motion (UMD global `Motion`) drives DOM-UI transitions. If it failed to load, every helper degrades to a plain element.
  const M = window.Motion || null;
  const mo = (tag) => (M && M.motion && M.motion[tag]) || tag;
  const MButton = mo('button'), MDiv = mo('div');
  const Presence = (M && M.AnimatePresence) || (({ children }) => children);
  const MotionRoot = ({ reduced, children }) => (M && M.MotionConfig ? <M.MotionConfig reducedMotion={reduced ? 'always' : 'user'}>{children}</M.MotionConfig> : children);
  const SPRING = { type: 'spring', stiffness: 420, damping: 30, mass: 0.7 };

  function Button({ children, onClick, variant = 'primary', className, disabled, icon, type = 'button', ...rest }) {
    const variants = { primary: 'btn btn-primary', ghost: 'btn btn-ghost', 'ghost-dark': 'btn btn-ghost-dark', teal: 'btn btn-teal', coral: 'btn btn-coral', amber: 'btn btn-amber' };
    const motionProps = M && !disabled ? { whileHover: { y: -1.5, scale: 1.02 }, whileTap: { scale: 0.97 }, transition: SPRING } : {};
    return (
      <MButton type={type} onClick={onClick} disabled={disabled} {...motionProps}
              className={cx(variants[variant], disabled && 'btn-disabled', className)} {...rest}>
        {icon && <span className="inline-flex" aria-hidden="true">{icon}</span>}
        {children}
      </MButton>
    );
  }

  // Wraps one screen: fades/slides in and out. `still` = opacity only (the game table must never sit inside a transformed ancestor).
  function ScreenFrame({ children, still }) {
    if (!M) return children;
    const p = still ? { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 } } : { initial: { opacity: 0, y: 14 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: -8 } };
    return <MDiv {...p} transition={{ duration: still ? 0.2 : 0.26, ease: [0.22, 1, 0.36, 1] }} style={{ minHeight: '100%' }}>{children}</MDiv>;
  }

  // Staggered entrance for lists of cards/buttons.
  function Stagger({ children, className, delay = 0.05 }) {
    if (!M) return <div className={className}>{children}</div>;
    const kids = React.Children.toArray(children);
    return (
      <MDiv className={className} initial="h" animate="s" variants={{ h: {}, s: { transition: { staggerChildren: delay, delayChildren: 0.08 } } }}>
        {kids.map((k, i) => <MDiv key={k.key || i} variants={{ h: { opacity: 0, y: 16 }, s: { opacity: 1, y: 0, transition: { duration: 0.32, ease: [0.22, 1, 0.36, 1] } } }}>{k}</MDiv>)}
      </MDiv>
    );
  }

  // ---------- Modal (Escape to close, focus moved inside) ----------
  function Modal({ children, onClose, label }) {
    const ref = useRef(null);
    useEffect(() => {
      const prev = document.activeElement;
      const first = ref.current && ref.current.querySelector('button, [tabindex="0"]');
      if (first) first.focus();
      const onKey = (e) => { if (e.key === 'Escape') { e.stopPropagation(); onClose && onClose(); } };
      document.addEventListener('keydown', onKey);
      return () => { document.removeEventListener('keydown', onKey); if (prev && prev.focus) prev.focus(); };
    }, []);
    return (
      <MDiv className="scrim" onClick={onClose}
            {...(M ? { initial: { opacity: 0 }, animate: { opacity: 1 }, transition: { duration: 0.18 } } : { })}>
        <MDiv ref={ref} role="dialog" aria-modal="true" aria-label={label} className="dialog"
             onClick={e => e.stopPropagation()}
             {...(M ? { initial: { opacity: 0, y: 24, scale: 0.96 }, animate: { opacity: 1, y: 0, scale: 1 }, transition: SPRING } : { })}>
          {children}
        </MDiv>
      </MDiv>
    );
  }

  // ---------- Card ----------
  // state props: selected / legal / illegal / interrupt. Legality is also shown with a glyph badge,
  // never by colour alone. `size` overrides --cw for this card only.
  function Card({ card, faceDown, size, selected, legal, illegal, interrupt, stick, armed, dim, onClick, onPointerDown, className, style, label, showDeclared, innerRef }) {
    const hidden = faceDown || !card || card.hidden || card.rank === 'HIDDEN';
    const interactive = !!onClick;
    const st = size ? { '--cw': size + 'px', ...style } : style;
    const onKey = interactive ? (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onClick(e); } } : undefined;
    const common = {
      ref: innerRef,
      style: st,
      onClick,
      onKeyDown: onKey,
      onPointerDown,
      'data-id': (!hidden && card && card.id) || undefined,
      role: interactive ? 'button' : undefined,
      tabIndex: interactive ? 0 : undefined,
      'aria-label': label,
      'aria-pressed': interactive && selected != null ? !!selected : undefined,
      'aria-disabled': illegal ? true : undefined
    };
    // One visual state per card. Precedence (they never legitimately overlap): selected > stick > interrupt > legal > illegal.
    const flags = cx(interactive && 'is-interactive', selected && 'is-selected', !selected && stick && 'is-stick', armed && 'tap-armed',
                     !selected && !stick && interrupt && 'is-interrupt', !selected && !stick && !interrupt && legal && 'is-legal',
                     !selected && !stick && illegal && 'is-illegal', dim && 'is-dim', className);
    // Playability is shown by the card itself (bright + lit = playable, gray = not playable); no icon is drawn for it.
    // Only the two special windows keep a label: a stick opportunity and an out-of-turn interrupt.
    const badge = !selected && stick ? <span className="badge stick" aria-hidden="true">STICK</span>
                : !selected && interrupt ? <span className="badge bolt" aria-hidden="true">{'⚡'}</span> : null;
    if (hidden) return <div className={cx('card is-back', flags)} {...common}>{badge}</div>;
    if (card.rank === 'JOKER') {
      // `card.as` is the engine's declared rank (only present once the Joker is on the table). It is rendered
      // as-is: there is no separate UI copy that could drift from the state the legality rules use.
      const decl = showDeclared ? card.as : null;
      return (
        <div className={cx('card is-face is-joker black', flags)} data-declared={decl || undefined} {...common}>
          <div className="idx joker-idx">
            {decl ? <><span className="r jr">{decl}</span><span className="s js">{'★'}</span></> : <span className="r js">{'★'}</span>}
          </div>
          <div className="j-photo" aria-hidden="true" />
          {decl
            ? <div className="j-decl"><span className="j-word">JOKER</span><span className="j-arrow">{'→'}</span><b className="j-rank">{decl}</b></div>
            : <div className="j-word-bottom"><span>JOKER</span></div>}
          {badge}
        </div>
      );
    }
    const red = SUIT_RED.has(card.suit);
    const glyph = SUIT_GLYPH[card.suit] || '';
    return (
      <div className={cx('card is-face', red ? 'red' : 'black', flags)} {...common}>
        <div className="idx"><span className="r">{card.rank}</span><span className="s">{glyph}</span></div>
        <div className="big">{glyph}</div>
        {badge}
      </div>
    );
  }

  window.SH = window.SH || {};
  window.SH.ui = Object.assign(window.SH.ui || {}, { cx, Icon, Button, Modal, Card, JokerFace, SUIT_GLYPH, M, MDiv, Presence, MotionRoot, ScreenFrame, Stagger, SPRING });
})();
