/* ============================================================
   PARTY — shared UI kit  (exports to window)
   ============================================================ */
const { useState, useEffect, useRef, useCallback } = React;

const sfx = (name) => window.partySounds?.play(name);

const PALETTE = ['#FF5A3C','#FFC22E','#9A6CFF','#16C2A3','#FF5C9D','#3D8BFF','#FF8A3C','#5CD6A0'];
const avatarColor = (name='?') => {
  let h=0; for (let i=0;i<name.length;i++) h=(h*31+name.charCodeAt(i))>>>0;
  return PALETTE[h % PALETTE.length];
};
const initials = (name='?') => name.trim()[0]?.toUpperCase() || '?';

/* ---------- array helpers ---------- */
function shuffle(arr){ const a=[...arr]; for(let i=a.length-1;i>0;i--){const j=Math.floor(Math.random()*(i+1));[a[i],a[j]]=[a[j],a[i]];} return a; }
function sample(arr,n){ return shuffle(arr).slice(0,n); }
function rand(arr){ return arr[Math.floor(Math.random()*arr.length)]; }

/* a "deck" that draws without replacement, reshuffling when exhausted */
function makeDeck(items){ let pool=shuffle(items); return { draw(n=1){ const out=[]; for(let i=0;i<n;i++){ if(!pool.length) pool=shuffle(items); out.push(pool.pop()); } return n===1?out[0]:out; }, remaining(){return pool.length;} }; }

/* ---------- confetti background ---------- */
function ConfettiBG({count=24}){
  const ref=useRef(null);
  useEffect(()=>{
    const cf=ref.current; if(!cf||cf.childElementCount) return;
    for(let i=0;i<count;i++){
      const d=document.createElement('div'); d.className='dot';
      const s=6+Math.random()*10; d.style.width=s+'px'; d.style.height=s+'px';
      d.style.left=Math.random()*100+'%'; d.style.background=PALETTE[i%PALETTE.length];
      d.style.borderRadius=Math.random()>.5?'50%':'2px';
      d.style.animationDuration=(7+Math.random()*9)+'s'; d.style.animationDelay=(-Math.random()*12)+'s';
      cf.appendChild(d);
    }
  },[]);
  return <div className="confetti" ref={ref}></div>;
}

/* ---------- celebration burst ---------- */
function Burst({fire, colors=PALETTE}){
  const ref=useRef(null);
  useEffect(()=>{
    if(!fire||!ref.current) return;
    sfx('celebrate');
    const host=ref.current; host.innerHTML='';
    for(let i=0;i<60;i++){
      const p=document.createElement('i');
      const s=7+Math.random()*9;
      p.style.cssText=`position:absolute;left:50%;top:40%;width:${s}px;height:${s}px;background:${colors[i%colors.length]};border-radius:${Math.random()>.5?'50%':'2px'};`;
      const ang=Math.random()*Math.PI*2, dist=120+Math.random()*260;
      const x=Math.cos(ang)*dist, y=Math.sin(ang)*dist - 120;
      p.animate([{transform:'translate(-50%,-50%) rotate(0)',opacity:1},{transform:`translate(${x}px,${y}px) rotate(${Math.random()*720-360}deg)`,opacity:0}],{duration:900+Math.random()*700,easing:'cubic-bezier(.2,.8,.3,1)',fill:'forwards'});
      host.appendChild(p);
    }
  },[fire]);
  return <div ref={ref} style={{position:'absolute',inset:0,pointerEvents:'none',zIndex:30,overflow:'hidden'}}></div>;
}

/* ---------- buttons ---------- */
function Btn({children,onClick,color,variant,size,disabled,style,sound='tap'}){
  const cls=['btn']; if(color)cls.push(color); if(variant)cls.push(variant); if(size)cls.push(size);
  const click=(e)=>{ if(!disabled && sound) sfx(sound); onClick&&onClick(e); };
  return <button className={cls.join(' ')} onClick={click} disabled={disabled} style={style}>{children}</button>;
}

/* ---------- top bar with back ---------- */
function TopBar({onBack,title,right,color}){
  return (
    <div className="topbar">
      {onBack ? <button className="iconbtn" onClick={onBack} aria-label="Back">‹</button> : <span style={{width:46}}/>}
      {title && <div className="gametag" style={{color:color||undefined}}>{title}</div>}
      <div style={{minWidth:46,display:'flex',justifyContent:'flex-end'}}>{right||null}</div>
    </div>
  );
}

/* ---------- avatar ---------- */
function Avatar({name,size=23}){
  return <span className="av" style={{background:avatarColor(name),width:size,height:size,borderRadius:size,fontSize:size*0.46}}>{initials(name)}</span>;
}
function Chip({name,onRemove,selectable,selected,onClick}){
  const cls=['chip']; if(selectable)cls.push('selectable'); if(selected)cls.push('sel');
  return (
    <div className={cls.join(' ')} onClick={onClick}>
      <Avatar name={name}/>{name}
      {onRemove && <span className="x" onClick={(e)=>{e.stopPropagation();onRemove();}}>✕</span>}
    </div>
  );
}

/* ---------- stepper ---------- */
function Stepper({value,min=0,max=99,onChange}){
  return (
    <div className="stepper">
      <button onClick={()=>onChange(Math.max(min,value-1))} disabled={value<=min}>–</button>
      <b>{value}</b>
      <button onClick={()=>onChange(Math.min(max,value+1))} disabled={value>=max}>+</button>
    </div>
  );
}

/* ---------- switch ---------- */
function Switch({on,onChange}){
  return <div className={'switch'+(on?' on':'')} onClick={()=>onChange(!on)} role="switch" aria-checked={on}></div>;
}

/* ---------- segmented control ---------- */
function Segmented({options,value,onChange,color='var(--grape)'}){
  return (
    <div style={{display:'flex',gap:7,flexWrap:'wrap'}}>
      {options.map(o=>{
        const v=typeof o==='string'?o:o.value, label=typeof o==='string'?o:o.label;
        const active=v===value;
        return <button key={v} onClick={()=>onChange(v)} style={{
          fontFamily:'var(--font-display)',fontWeight:700,fontSize:15,padding:'9px 15px',borderRadius:40,cursor:'pointer',
          border:'2.5px solid '+(active?'transparent':'rgba(255,255,255,.18)'),
          background:active?color:'transparent',color:active?'#fff':'#C9BCEC',transition:'all .15s'
        }}>{label}</button>;
      })}
    </div>
  );
}

/* ---------- countdown timer hook ---------- */
function useCountdown(seconds, running, onEnd){
  const [remaining,setRemaining]=useState(seconds);
  const endRef=useRef(onEnd);
  const prevRem=useRef(seconds);
  endRef.current=onEnd;
  useEffect(()=>{ setRemaining(seconds); prevRem.current=seconds; },[seconds]);
  useEffect(()=>{
    if(!running) return;
    if(remaining<=0){ endRef.current&&endRef.current(); return; }
    if(running && remaining < prevRem.current){
      if(remaining <= 3) sfx('warn');
      else if(remaining <= 5) sfx('tick');
    }
    prevRem.current=remaining;
    const t=setTimeout(()=>setRemaining(r=>r-1),1000);
    return ()=>clearTimeout(t);
  },[running,remaining]);
  return [remaining,setRemaining];
}
function TimerBar({remaining,total}){
  const pct=Math.max(0,(remaining/total)*100);
  const cls=remaining<=Math.max(5,total*0.15)?'danger':remaining<=total*0.4?'warn':'';
  return <div className="timerbar"><i className={cls} style={{width:pct+'%'}}/></div>;
}
function fmtTime(s){ const m=Math.floor(s/60), ss=s%60; return m>0?`${m}:${String(ss).padStart(2,'0')}`:`${ss}`; }

/* ---------- 3-2-1 countdown overlay ---------- */
function Countdown({from=3,onDone,go='GO!'}){
  const [n,setN]=useState(from);
  useEffect(()=>{
    if(n<0){ onDone&&onDone(); return; }
    if(n===0) sfx('go');
    else sfx('tick');
    const t=setTimeout(()=>setN(n-1), n===0?700:800);
    return ()=>clearTimeout(t);
  },[n]);
  return (
    <div className="center" style={{flex:1,minHeight:'60vh'}}>
      <div key={n} className="bigcount count-anim" style={{color:n===0?'var(--teal)':'var(--cream)'}}>{n===0?go:n}</div>
    </div>
  );
}

/* ============================================================
   PrivateReveal — the "pass the device" secret loop.
   props:
     players: [name,...]
     accent:  css color
     getContent(name, idx) => JSX  (the secret screen)
     passLabel(name) => string     (optional)
     onDone()
   ============================================================ */
function PrivateReveal({players, accent='var(--grape)', getContent, onDone, doneLabel='Everyone\'s seen it'}){
  const [idx,setIdx]=useState(0);
  const [shown,setShown]=useState(false);
  const name=players[idx];
  const last=idx>=players.length-1;

  if(idx>=players.length){
    return (
      <div className="reveal-screen screen">
        <div className="display" style={{fontSize:40,lineHeight:.95}}>All set!</div>
        <p className="muted mt16" style={{maxWidth:300}}>Everyone has their secret. Put the device down where the group can see it.</p>
        <div style={{width:'100%',maxWidth:340,marginTop:28}}><Btn color="teal" size="lg" onClick={onDone}>Start the round →</Btn></div>
      </div>
    );
  }

  if(!shown){
    return (
      <div className="reveal-screen screen" key={'pass'+idx}>
        <div className="kicker">Pass the phone to</div>
        <div className="mt16" style={{display:'flex',flexDirection:'column',alignItems:'center',gap:18}}>
          <div style={{transform:'scale(1.8)'}}><Avatar name={name} size={46}/></div>
          <div className="display wiggle" style={{fontSize:52}}>{name}</div>
        </div>
        <p className="muted mt16">Everyone else, look away! 👀</p>
        <div style={{width:'100%',maxWidth:340,marginTop:30}}>
          <Btn color="grape" size="lg" sound="pass" onClick={()=>setShown(true)} style={accent?{background:accent,color:'#fff'}:undefined}>Tap to reveal</Btn>
        </div>
        <div className="mono mt24" style={{fontSize:12,color:'#7E70AE'}}>{idx+1} / {players.length}</div>
      </div>
    );
  }

  return (
    <div className="reveal-screen screen" key={'show'+idx}>
      {getContent(name, idx)}
      <div style={{width:'100%',maxWidth:340,marginTop:28}}>
        <Btn variant="ghost" size="lg" onClick={()=>{ setShown(false); setIdx(idx+1); }}>
          {last? '✓ '+doneLabel : 'Hide & pass on →'}
        </Btn>
      </div>
    </div>
  );
}

/* ---------- scoreboard ---------- */
function ScoreBoard({scores, players, accent='var(--gold)'}){
  const ranked=[...players].map(p=>({p,s:scores[p]||0})).sort((a,b)=>b.s-a.s);
  const top=ranked[0]?.s||0;
  return (
    <div className="card" style={{padding:'16px 18px'}}>
      <div className="col" style={{gap:10}}>
        {ranked.map((r,i)=>(
          <div key={r.p} className="row" style={{gap:12}}>
            <span className="mono" style={{width:20,color:'#897BB6',fontSize:13}}>{i+1}</span>
            <Avatar name={r.p}/>
            <b style={{flex:1,fontSize:16}}>{r.p}</b>
            <span style={{fontFamily:'var(--font-display)',fontWeight:800,fontSize:20,color:r.s===top&&top>0?accent==='var(--gold)'?'#C98A00':accent:'var(--ink)'}}>{r.s}</span>
          </div>
        ))}
      </div>
    </div>
  );
}

/* ---------- device tilt (Heads Up mode) ---------- */
const TILT_CORRECT = -25;
const TILT_SKIP = 25;

function useTiltActions({ onCorrect, onSkip, enabled, cooldownMs = 900 }) {
  const [tiltAvailable, setTiltAvailable] = useState(false);
  const [permissionState, setPermissionState] = useState('unknown'); // unknown | needed | granted | denied
  const lastFire = useRef(0);
  const correctRef = useRef(onCorrect);
  const skipRef = useRef(onSkip);
  correctRef.current = onCorrect;
  skipRef.current = onSkip;

  const needsPermission =
    typeof DeviceOrientationEvent !== 'undefined' &&
    typeof DeviceOrientationEvent.requestPermission === 'function';

  useEffect(() => {
    if (!enabled) return;
    if (typeof DeviceOrientationEvent === 'undefined') {
      setTiltAvailable(false);
      setPermissionState('denied');
      return;
    }
    if (needsPermission) {
      setPermissionState('needed');
      return;
    }
    setPermissionState('granted');
    setTiltAvailable(true);
  }, [enabled, needsPermission]);

  useEffect(() => {
    if (!enabled || permissionState !== 'granted') return;
    const handler = (e) => {
      const beta = e.beta;
      if (beta == null) return;
      const now = Date.now();
      if (now - lastFire.current < cooldownMs) return;
      if (beta < TILT_CORRECT) {
        lastFire.current = now;
        correctRef.current && correctRef.current();
      } else if (beta > TILT_SKIP) {
        lastFire.current = now;
        skipRef.current && skipRef.current();
      }
    };
    window.addEventListener('deviceorientation', handler);
    return () => window.removeEventListener('deviceorientation', handler);
  }, [enabled, permissionState, cooldownMs]);

  const requestPermission = useCallback(async () => {
    if (!needsPermission) {
      setPermissionState('granted');
      setTiltAvailable(true);
      return true;
    }
    try {
      const res = await DeviceOrientationEvent.requestPermission();
      if (res === 'granted') {
        setPermissionState('granted');
        setTiltAvailable(true);
        return true;
      }
      setPermissionState('denied');
      setTiltAvailable(false);
      return false;
    } catch {
      setPermissionState('denied');
      setTiltAvailable(false);
      return false;
    }
  }, [needsPermission]);

  return { tiltAvailable, permissionState, requestPermission, needsPermission };
}

/* ---------- generic centered moment screen ---------- */
function Moment({stamp,shadow='sh-ink',sub,children,size=72}){
  return (
    <div className="center screen" style={{flex:1,padding:'30px 26px',gap:8}}>
      {stamp && <div className={'stamp '+shadow} style={{fontSize:size}}>{stamp}</div>}
      {sub && <p className="serif-i mt16" style={{fontSize:22,color:'#FFD79A'}}>{sub}</p>}
      {children}
    </div>
  );
}

/* ---------- leave-game confirmation ---------- */
function ExitConfirm({open,gameName,onStay,onLeave}){
  if(!open) return null;
  return (
    <div className="exit-overlay" onClick={onStay} role="dialog" aria-modal="true" aria-labelledby="exit-title">
      <div className="exit-sheet" onClick={e=>e.stopPropagation()}>
        <div className="stamp sh-coral" id="exit-title" style={{fontSize:34}}>Leave game?</div>
        <p className="muted" style={{marginTop:12,fontSize:15,maxWidth:280}}>
          {gameName ? `You'll lose your spot in ${gameName}.` : 'Your progress in this game will be lost.'}
        </p>
        <div className="col" style={{gap:11,marginTop:24,width:'100%'}}>
          <Btn color="teal" size="lg" onClick={onStay}>Keep playing</Btn>
          <Btn variant="ghost" onClick={onLeave}>Leave → home</Btn>
        </div>
      </div>
    </div>
  );
}

/* ---------- secret pass-around ballot (one player at a time) ---------- */
function SecretBallot({players,accent='var(--grape)',btnColor='grape',onComplete,renderVote,completeLabel='Reveal →',autoComplete=false}){
  const [idx,setIdx]=useState(0);
  const [shown,setShown]=useState(false);
  const [votes,setVotes]=useState({});
  const name=players[idx];
  const doneRef=useRef(onComplete);
  doneRef.current=onComplete;

  const advance=(value)=>{
    const v={...votes,[name]:value};
    setVotes(v);
    setShown(false);
    const next=idx+1;
    setIdx(next);
    if(autoComplete&&next>=players.length) doneRef.current&&doneRef.current(v);
  };

  if(idx>=players.length){
    if(autoComplete) return null;
    return (
      <div className="center reveal-screen" style={{flex:1,padding:'20px 26px'}}>
        <p className="muted" style={{marginBottom:18,fontSize:14}}>Everyone has voted in secret.</p>
        <div style={{width:'100%',maxWidth:340}}>
          <Btn color={btnColor} size="lg" onClick={()=>onComplete(votes)}>{completeLabel}</Btn>
        </div>
      </div>
    );
  }

  if(!shown){
    return (
      <div className="reveal-screen screen" key={'pass'+idx}>
        <div className="kicker">Pass the phone to</div>
        <div className="mt16" style={{display:'flex',flexDirection:'column',alignItems:'center',gap:18}}>
          <div style={{transform:'scale(1.8)'}}><Avatar name={name} size={46}/></div>
          <div className="display wiggle" style={{fontSize:52}}>{name}</div>
        </div>
        <p className="muted mt16">Everyone else, look away! 👀</p>
        <div style={{width:'100%',maxWidth:340,marginTop:30}}>
          <Btn color={btnColor} size="lg" sound="pass" onClick={()=>setShown(true)} style={accent?{background:accent,color:'#fff'}:undefined}>Tap to vote</Btn>
        </div>
        <div className="mono mt24" style={{fontSize:12,color:'#7E70AE'}}>{idx+1} / {players.length}</div>
      </div>
    );
  }

  return (
    <div className="reveal-screen screen" key={'vote'+idx}>
      {renderVote(name,advance)}
      <div className="mono mt24" style={{fontSize:12,color:'#7E70AE'}}>your vote stays secret</div>
    </div>
  );
}

/* ---------- sound toggle ---------- */
function SoundToggle({style}){
  const [on,setOn]=useState(()=>window.partySounds?.isEnabled()??true);
  const toggle=()=>{
    const next=!on;
    setOn(next);
    window.partySounds?.setEnabled(next);
    if(next){ window.partySounds?.resume(); window.partySounds?.play('tap'); }
  };
  return (
    <button type="button" className="iconbtn sound-toggle" onClick={toggle}
      aria-label={on?'Mute sound effects':'Enable sound effects'} aria-pressed={on} style={style}>
      {on?'🔊':'🔇'}
    </button>
  );
}

/* export */
Object.assign(window,{
  PALETTE, avatarColor, initials, shuffle, sample, rand, makeDeck,
  ConfettiBG, Burst, Btn, TopBar, Avatar, Chip, Stepper, Switch, Segmented,
  useCountdown, TimerBar, fmtTime, Countdown, PrivateReveal, ScoreBoard, Moment,
  useTiltActions, TILT_CORRECT, TILT_SKIP, ExitConfirm, SecretBallot, SoundToggle,
});
