/* ============================================================
   APP SHELL — entry → roster → password-gated Main / Moderator
   ============================================================ */
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "accent": "#f0a836",
  "glassTint": 0.02,
  "shake": 1,
  "embers": 1
}/*EDITMODE-END*/;

const CLASS_ICON = { Paladin:'shield', Rogue:'dagger', Sorcerer:'staff', Cleric:'mace', Fighter:'sword', Ranger:'bow' };
const clampN = (v,min,max)=> Math.max(min, Math.min(max, v));

/* fixed corner button: toggle real fullscreen (with webkit fallbacks for Android/Safari) */
function FullscreenButton(){
  const [fs, setFs] = React.useState(false);
  React.useEffect(()=>{
    const on = ()=> setFs(!!(document.fullscreenElement || document.webkitFullscreenElement));
    document.addEventListener('fullscreenchange', on);
    document.addEventListener('webkitfullscreenchange', on);
    return ()=>{ document.removeEventListener('fullscreenchange', on); document.removeEventListener('webkitfullscreenchange', on); };
  }, []);
  const toggle = ()=>{
    const d = document, el = d.documentElement;
    const isFs = d.fullscreenElement || d.webkitFullscreenElement;
    try{
      if(!isFs){
        const req = el.requestFullscreen || el.webkitRequestFullscreen || el.webkitRequestFullScreen || el.msRequestFullscreen;
        if(req) req.call(el, { navigationUI:'hide' });
      } else {
        const ex = d.exitFullscreen || d.webkitExitFullscreen || d.msExitFullscreen;
        if(ex) ex.call(d);
      }
    }catch(_){}
  };
  return (
    <div className="fs-btn glass-btn" onClick={toggle} title={fs?'Exit full screen':'Full screen'}>
      {fs ? (
        <svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M9 9H5V5"/><path d="M15 9h4V5"/><path d="M9 15H5v4"/><path d="M15 15h4v4"/></svg>
      ) : (
        <svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M4 9V5h4"/><path d="M20 9V5h-4"/><path d="M4 15v4h4"/><path d="M20 15v4h-4"/></svg>
      )}
    </div>
  );
}

// hex → "H S% L%" for the --primary HSL var
function hexToHslParts(hex){
  let h = hex.replace('#',''); if(h.length===3) h=h.split('').map(c=>c+c).join('');
  const r=parseInt(h.slice(0,2),16)/255, g=parseInt(h.slice(2,4),16)/255, b=parseInt(h.slice(4,6),16)/255;
  const mx=Math.max(r,g,b), mn=Math.min(r,g,b); let hue,s,l=(mx+mn)/2;
  if(mx===mn){ hue=0; s=0; }
  else { const d=mx-mn; s=l>0.5?d/(2-mx-mn):d/(mx+mn);
    switch(mx){ case r: hue=(g-b)/d+(g<b?6:0); break; case g: hue=(b-r)/d+2; break; default: hue=(r-g)/d+4; } hue/=6; }
  return `${Math.round(hue*360)} ${Math.round(s*100)}% ${Math.round(l*100)}%`;
}

/* ============================================================
   ENTRY ARTIFACT — a CSS/SVG focal object the player clicks to enter.
   Four moderator-selectable placeholders; swap for real art later.
   ============================================================ */
function EntryArtifact({ kind }){
  if(kind==='brazier') return (
    <div className="art-obj art-brazier">
      <div className="brz-glow"></div>
      <div className="brz-bowl"></div>
      <div className="brz-flame"><span></span><span></span><span></span></div>
      <div className="brz-coals"></div>
    </div>
  );
  if(kind==='tome') return (
    <div className="art-obj art-tome">
      <div className="tome-glow"></div>
      <svg viewBox="0 0 200 150" className="tome-svg">
        <path d="M100 24 C70 8 30 10 14 20 L14 128 C30 118 70 116 100 132 C130 116 170 118 186 128 L186 20 C170 10 130 8 100 24 Z" className="tome-cover"/>
        <path d="M100 24 L100 132" className="tome-spine"/>
        <path d="M22 30 C40 24 72 24 92 36 L92 122 C72 110 40 110 22 116 Z" className="tome-page"/>
        <path d="M178 30 C160 24 128 24 108 36 L108 122 C128 110 160 110 178 116 Z" className="tome-page"/>
      </svg>
      <div className="tome-runes"><span>✦</span><span>❂</span><span>✷</span></div>
    </div>
  );
  if(kind==='rune') return (
    <div className="art-obj art-rune">
      <div className="rune-glow"></div>
      <svg viewBox="0 0 200 200" className="rune-ring rune-ring-1"><circle cx="100" cy="100" r="92"/></svg>
      <svg viewBox="0 0 200 200" className="rune-ring rune-ring-2"><circle cx="100" cy="100" r="70" strokeDasharray="6 10"/></svg>
      <svg viewBox="0 0 200 200" className="rune-ring rune-ring-3"><polygon points="100,30 160,135 40,135"/></svg>
      <div className="rune-core">✦</div>
    </div>
  );
  // default: torch-crest amulet
  return (
    <div className="art-obj art-amulet">
      <div className="amu-glow"></div>
      <svg viewBox="0 0 200 240" className="amu-svg">
        <path d="M100 14 L150 40 L150 96 C150 150 128 196 100 222 C72 196 50 150 50 96 L50 40 Z" className="amu-crest"/>
        <path d="M100 30 L138 50 L138 94 C138 138 120 176 100 198 C80 176 62 138 62 94 L62 50 Z" className="amu-inner"/>
      </svg>
      <div className="amu-flame"><span></span><span></span></div>
    </div>
  );
}

/* ---------- ambient dust motes drifting through the scene ---------- */
function DustField({ count=24 }){
  const n = window.LOW_PERF ? Math.max(6, Math.round(count*0.4)) : count;
  const motes = React.useMemo(()=> Array.from({length:n}, ()=>({
    left: Math.random()*100, top: Math.random()*100,
    size: 2 + Math.random()*6,
    dur: 16 + Math.random()*24, delay: -Math.random()*36,
    drift: (Math.random()*2-1)*44, op: 0.22 + Math.random()*0.5,
  })), [count]);
  return (
    <div className="dust-field" aria-hidden="true">
      {motes.map((m,i)=>(
        <span key={i} className="dust-mote" style={{
          left:m.left+'%', top:m.top+'%', width:m.size+'px', height:m.size+'px',
          '--dur':m.dur+'s', '--delay':m.delay+'s', '--drift':m.drift+'px', '--op':m.op,
        }}/>
      ))}
    </div>
  );
}

/* ---------- entry: Genshin-style "ignite to enter" gate ---------- */
function EnterGate({ onEnter }){
  const game = useGame();
  const kind = (game.entry && game.entry.artifact) || 'amulet';
  const [phase, setPhase] = useState('idle');   // idle → igniting → out
  const sceneRef = useRef(null);

  // pointer / device-tilt parallax across the scene layers
  useEffect(()=>{
    const el = sceneRef.current; if(!el) return;
    let raf=0, tx=0,ty=0,cx=0,cy=0;
    const loop=()=>{ cx+=(tx-cx)*0.08; cy+=(ty-cy)*0.08;
      el.style.setProperty('--mx', cx.toFixed(2)); el.style.setProperty('--my', cy.toFixed(2));
      if(Math.abs(tx-cx)>0.001||Math.abs(ty-cy)>0.001) raf=requestAnimationFrame(loop); else raf=0; };
    const kick=()=>{ if(!raf) raf=requestAnimationFrame(loop); };
    const move=(e)=>{ const t=e.touches?e.touches[0]:e; tx=(t.clientX/window.innerWidth-0.5)*2; ty=(t.clientY/window.innerHeight-0.5)*2; kick(); };
    const orient=(e)=>{ if(e.gamma==null) return; tx=Math.max(-1,Math.min(1,e.gamma/30)); ty=Math.max(-1,Math.min(1,(e.beta-40)/30)); kick(); };
    window.addEventListener('pointermove',move);
    window.addEventListener('deviceorientation',orient);
    return ()=>{ window.removeEventListener('pointermove',move); window.removeEventListener('deviceorientation',orient); cancelAnimationFrame(raf); };
  },[]);

  const ignite = ()=>{
    if(phase!=='idle') return;
    setPhase('zooming');
    audio.unlock();
    try{ audio.play('gateOpen'); }catch(_){}
    try{ if(navigator.vibrate) navigator.vibrate([10,40,120]); }catch(_){}
    // camera pushes into the stone, then hand off to the realm
    setTimeout(()=> onEnter(), 1380);
  };

  return (
    <div ref={sceneRef} className={`enter-scene phase-${phase}`}>
      <div className="es-bg"></div>
      <div className="es-fog es-fog-1"></div>
      <div className="es-fog es-fog-2"></div>
      <div className="es-vignette"></div>
      <Embers count={26}/>
      <DustField count={26}/>

      <div className="es-wordmark">
        <h1 className="display flicker">Torchlit</h1>
        <p className="es-tag">A realm of candlelight &amp; steel</p>
      </div>

      {/* the altar: pendant centered, glowing stone in its heart (the zoom target) */}
      <div className="es-altar">
        <img className="es-pendant" src="assets/entry-pendant.png" alt="" draggable="false"/>
        <button className="es-stone-btn" onClick={ignite} aria-label="Touch the stone to enter">
          <span className="es-stone-glow"></span>
          <span className="es-stone-ring"></span>
          <span className="es-stone-ring d2"></span>
          <span className="es-stone-spec"></span>
        </button>
      </div>

      <div className="es-cta">
        <span className="es-cta-line"></span>
        <span className="es-cta-text">{phase==='idle' ? 'Touch the stone to enter' : 'Entering the realm…'}</span>
        <span className="es-cta-line"></span>
      </div>

      {/* turquoise → white bloom that swallows the screen as the camera dives in */}
      <div className="es-flash"></div>
    </div>
  );
}

/* ---------- room access is handled entirely by the Create / Join /
   Display landing (per-room passwords). The old fixed-passcode gate
   has been removed. ---------- */

function App(){
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const game = useGame();
  const net = useNet();
  const auth = (window.useAuthUser && useAuthUser()) || { user:null, loading:false };
  const [authSkipped, setAuthSkipped] = useState(false);
  const [showCampaigns, setShowCampaigns] = useState(false);
  const dev = (window.getDeviceId && getDeviceId()) || '';
  const [pid, setPid] = useState(()=> localStorage.getItem('torchlit-pid') || '');
  const [role, setRole] = useState('select');          // host | main | player | select
  const [roomJoined, setRoomJoined] = useState(false);
  const [entered, setEntered] = useState(false);
  const [warp, setWarp] = useState(false);
  const [muted, setMuted] = useState(false);

  // auto-reconnect: a returning device skips the pendant + landing and goes
  // straight back to its seat (the reconnection veil covers the gap).
  useEffect(()=>{
    const saved = (window.loadRoom && loadRoom()) || {code:'',role:''};
    if(saved.code && saved.role){
      try{ store.connectRoom(saved.code, { role:saved.role, name:saved.name }); }catch(_){}
      setRole(saved.role); setRoomJoined(true); setEntered(true);
    }
  }, []);

  // apply tweaks to CSS vars
  useEffect(()=>{
    const r = document.documentElement;
    r.style.setProperty('--primary', hexToHslParts(t.accent));
    r.style.setProperty('--card-glow', hexToHslParts(t.accent));
    r.style.setProperty('--glass-tint', String(t.glassTint));
    r.style.setProperty('--shake-scale', String(t.shake));
    r.style.setProperty('--ember-opacity', String(t.embers));
  }, [t]);

  // ---- responsive root-scaler — fits the UI to ANY viewport so no manual
  // browser zoom is ever needed. The app is largely rem-based, so scaling the
  // root font-size grows/shrinks text, panels, gaps & icon boxes together.
  // Reference frames differ per screen (a phone vs. a shared display). ----
  const scaleScreen = !entered ? 'landing' : !roomJoined ? 'landing' : role;
  useEffect(()=>{
    const fit = ()=>{
      const w = window.innerWidth, h = window.innerHeight;
      let px = 16;
      if(scaleScreen==='main'){
        // shared display — fit a 1366×768 frame on BOTH axes, letterbox-style
        px = 16 * clampN(Math.min(w/1366, h/768), 0.58, 1.5);
      } else if(scaleScreen==='player'){
        // handset — scale to width (414 ref); gentle cap so a desktop window isn't huge
        px = 16 * clampN(w/414, 0.82, 1.35);
      } else if(scaleScreen==='host'){
        px = 16 * clampN(w/1180, 0.8, 1.12);
      } else {
        // landing / create / join
        px = 16 * clampN(Math.min(w/1100, h/780), 0.78, 1.18);
      }
      document.documentElement.style.fontSize = px.toFixed(2)+'px';
    };
    fit();
    window.addEventListener('resize', fit);
    window.addEventListener('orientationchange', fit);
    return ()=>{ window.removeEventListener('resize', fit); window.removeEventListener('orientationchange', fit); };
  }, [scaleScreen]);

  const enter = ()=>{
    audio.unlock();
    setEntered(true);
    setWarp(true);                         // white-flash + blur-in handoff
    setTimeout(()=> setWarp(false), 1000);
  };
  // room landing finished — take the chosen seat
  const onRoomJoined = ({ role:r })=>{
    if(window.store) store.setPresenceMeta(r);
    setRole(r); setRoomJoined(true);
    setWarp(true); setTimeout(()=> setWarp(false), 1000);
  };
  const createHero = (data)=>{
    const id = Game.createPlayer({ ...data, deviceId: dev });
    setPid(id);
    localStorage.setItem('torchlit-pid', id);
    localStorage.setItem('torchlit-claimed','1');
    setRole('player');
  };
  const unlock = (which)=>{ setRole(which); };
  const leaveSeat = ()=>{
    try{ if(window.Room) Room.leave(); }catch(_){}
    if(window.clearRoom) clearRoom();
    setRoomJoined(false); setRole('select');
  };
  const toggleMute = ()=>{ const m=!muted; setMuted(m); audio.setMuted(m); };

  // my hero is the one bound to THIS device (robust across reloads), else my saved pid
  const myHero = game.players.find(p=>p.deviceId===dev) || (pid && game.players.find(p=>p.id===pid));
  const myPid = myHero ? myHero.id : pid;
  const showCreate = roomJoined && role==='player' && !myHero;

  // Phase 1 auth: once past the pendant, identify the player (guest or
  // account) before the room landing. Falls through gracefully if the
  // auth client is unavailable, so the app never bricks on a CDN hiccup.
  const needAuth = !!window.AuthGate && entered && !roomJoined
    && !auth.loading && !auth.user && !authSkipped;

  return (
    <>
      {!entered && <EnterGate onEnter={enter}/>}

      {/* sign-in — guest / login, layered after the pendant */}
      {needAuth && <AuthGate onSkip={()=>setAuthSkipped(true)}/>}

      {/* landing — Create / Join / Display, after the pendant + sign-in */}
      {entered && !roomJoined && !needAuth && !auth.loading && <RoomLanding onJoined={onRoomJoined}/>}

      {/* account chip — who you are + "Save your progress" upgrade (landing only) */}
      {entered && !roomJoined && window.AccountMenu && <window.AccountMenu/>}

      {/* My Campaigns — manage account-owned campaigns (signed-in, landing only) */}
      {entered && !roomJoined && auth.user && window.CampaignManager && (
        <button className="camp-launch glass-btn" onClick={()=>setShowCampaigns(true)} title="My Campaigns">
          <Icon name="book" size={15}/> My Campaigns
        </button>
      )}
      {showCampaigns && window.CampaignManager && <CampaignManager onClose={()=>setShowCampaigns(false)}/>}

      {entered && roomJoined && showCreate && (
        <CharacterCreate onCreate={createHero}/>
      )}

      {/* mute — always available once entered */}
      {entered && (
        <div className="mute-btn liquid-glass glass-btn" onClick={toggleMute} title={muted?'Unmute':'Mute'}>
          <Icon name={muted?'item':'music'} size={18}/>
        </div>
      )}

      {/* full screen — available on every screen, including the landing */}
      <FullscreenButton/>

      {/* live-sync status — only meaningful once seated in a room */}
      {entered && roomJoined && (
        <div className={`net-pill net-${net.status}`} title={`Live sync · room ${net.room||'—'}`}>
          <span className="net-dot"></span>
          {net.status==='online' ? 'Synced' : net.status==='connecting' ? 'Connecting…' : 'Offline'}
        </div>
      )}

      {/* active screen */}
      {entered && roomJoined && role==='player' && myHero && <>
        <PlayerScreen playerId={myPid}/>
        <button className="lock-corner glass-btn" title="Leave room" onClick={leaveSeat}>
          <Icon name="key" size={16}/>
        </button>
      </>}
      {entered && roomJoined && role==='main' && <>
        <MainScreen/>
        {window.CardPlayMain && <window.CardPlayMain/>}
        {window.DiceMain && <window.DiceMain/>}
        <button className="leave-seat glass-btn" onClick={leaveSeat} title="Leave this seat">✕ Exit</button>
      </>}
      {entered && roomJoined && role==='host' && <>
        <HostScreen/>
        <button className="leave-seat glass-btn" onClick={leaveSeat} title="Leave this seat">✕ Exit</button>
      </>}

      {/* reconnection overlay — covers drops while in a seat */}
      {entered && roomJoined && window.ReconnectVeil && <window.ReconnectVeil/>}

      {/* white-flash + blur-in handoff from the entry zoom */}
      {warp && <div className="realm-veil"></div>}

      {/* tweaks */}
      <TweaksPanel title="Tweaks">
        <TweakSection label="Look"/>
        <TweakColor label="Ember accent" value={t.accent}
          options={['#f0a836','#e8743b','#d94f4f','#52b07a','#5a8fd6','#b07ad6']}
          onChange={v=>setTweak('accent',v)}/>
        <TweakSlider label="Glass tint" value={t.glassTint} min={0} max={0.12} step={0.01}
          onChange={v=>setTweak('glassTint',v)}/>
        <TweakSlider label="Embers" value={t.embers} min={0} max={1} step={0.1}
          onChange={v=>setTweak('embers',v)}/>
        <TweakSection label="Feedback"/>
        <TweakSlider label="Shake intensity" value={t.shake} min={0} max={2.5} step={0.1} unit="×"
          onChange={v=>setTweak('shake',v)}/>
        <TweakSection label="Demo"/>
        <TweakButton label="Reset session" onClick={()=>Game.resetAll()}/>
        <TweakButton label="Forget this device" onClick={()=>{ localStorage.removeItem('torchlit-claimed'); location.reload(); }}/>
      </TweaksPanel>
    </>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App/>);
