import React,{useEffect,useLayoutEffect,useRef,useState,memo} from 'react';
import {createRoot} from 'react-dom/client';
import {mountCodeViewer} from '../web/code-viewer.mjs';
import {reconcile,saveOnServer,type Reservation,type Scenario} from './model';
mountCodeViewer();
const original:Reservation={id:'booking-17',name:'Community workshop',seats:2,version:1};
type Panel={editor:Reservation;summary:Reservation;server:Reservation;status:string;events:string[]};
const fresh=():Panel=>({editor:{...original},summary:{...original},server:{...original},status:'Ready to save.',events:[]});
const schedule=(signal:AbortSignal)=>new Promise<void>((resolve,reject)=>{const t=setTimeout(resolve,450);signal.addEventListener('abort',()=>{clearTimeout(t);reject(new DOMException('Reset','AbortError'));},{once:true});});
function Keys(){const [flipped,setFlipped]=useState(false),people=[{id:'a',name:'Aster'},{id:'b',name:'Birch'}],rows=flipped?[...people].reverse():people;
 return <section className="lab-section"><h2>Which row owns the input?</h2><p>Type a note beside Aster, then reverse the rows. Index keys attach the note to a position. Stable keys keep it with the person. These are real uncontrolled React inputs.</p><button className="secondary" onClick={()=>setFlipped(v=>!v)}>Reverse the rows</button><div className="experiment-grid">{[false,true].map(safe=><article className="result-card" key={String(safe)}><h3>{safe?'Stable identity keys':'Array index keys'}</h3>{rows.map((p,i)=><label key={safe?p.id:i}>{p.name}<input aria-label={`${safe?'Stable':'Index'} note for ${p.name}`} defaultValue="" placeholder="Type a note" maxLength={80}/></label>)}</article>)}</div></section>;
}
let naiveRenders=0,memoRenders=0;
const MemoTile=memo(function Tile({label}:{label:string}){memoRenders++;return <span>{label} </span>;});
function PlainTile({label}:{label:string}){naiveRenders++;return <span>{label} </span>;}
function RenderDemo(){const [count,setCount]=useState(0),plain=useRef<HTMLSpanElement>(null),cached=useRef<HTMLSpanElement>(null);useLayoutEffect(()=>{if(plain.current)plain.current.textContent=String(naiveRenders);if(cached.current)cached.current.textContent=String(memoRenders);});
 return <section className="lab-section"><h2>What actually rendered?</h2><p>Update an unrelated counter. Both groups receive unchanged text props. The memoized children can skip rendering; the plain children render again. Counts are component invocations in this production build, not speed measurements.</p><button className="secondary" onClick={()=>setCount(v=>v+1)}>Update unrelated counter ({count})</button><div className="experiment-grid"><article className="result-card"><h3>Plain children: <span ref={plain}>0</span> renders</h3>{['North','South','East'].map(label=><PlainTile key={label} label={label}/>)}</article><article className="result-card"><h3>Memoized children: <span ref={cached}>0</span> renders</h3>{['North','South','East'].map(label=><MemoTile key={label} label={label}/>)}</article></div></section>;
}
function App(){
 const [scenario,setScenario]=useState<Scenario>('rejected'),[panels,setPanels]=useState<Panel[]>([fresh(),fresh()]),[busy,setBusy]=useState(false),[status,setStatus]=useState('Choose a scenario, then run both saves.'),[resetKey,setResetKey]=useState(0);
 const controller=useRef<AbortController|null>(null),generation=useRef(0),error=useRef<HTMLParagraphElement>(null);
 useEffect(()=>()=>controller.current?.abort(),[]);
 async function run(){controller.current?.abort();const abort=new AbortController();controller.current=abort;const id=++generation.current;setBusy(true);
  const requested={...original,seats:scenario==='rejected'?6:3};const server=scenario==='conflict'?{...original,seats:4,version:2}:{...original};
  setPanels([false,true].map(()=>({editor:requested,summary:{...original},server,status:'Saving…',events:['Show the proposed change while the request is pending.']})));setStatus('The screen updates before the simulated server responds.');
  try{await schedule(abort.signal);if(id!==generation.current)return;
   setPanels([false,true].map(safe=>{const result=saveOnServer(server,requested,scenario,safe),state=reconcile(original,requested,result,safe);return {...state,server:result.record,events:[`Submitted version ${requested.version}; current server version ${server.version}.`,result.ok?'Server accepted the change.':result.reason,safe?(result.ok?'Refresh the summary from the confirmed record.':'Display the server record and an actionable error.'):'Broken handler leaves optimism or a stale summary visible.']};}));
   setStatus('Comparison complete. Compare the editor, cached summary, and server record.');setBusy(false);if(scenario!=='cache')setTimeout(()=>{if(id===generation.current)error.current?.focus();},0);
  }catch(e){if(!(e instanceof DOMException&&e.name==='AbortError')){setStatus('The save could not complete. Reset and try again.');setBusy(false);}}
 }
 function reset(){generation.current++;controller.current?.abort();naiveRenders=0;memoRenders=0;setResetKey(v=>v+1);setPanels([fresh(),fresh()]);setBusy(false);setStatus('Reset complete. Pending saves, row notes and render counters were discarded.');}
 return <><div className="lab-control-row"><label>Scenario<select id="scenario" value={scenario} disabled={busy} onChange={e=>setScenario(e.target.value as Scenario)}><option value="rejected">The server rejects a save</option><option value="conflict">Another editor saved first</option><option value="cache">A summary still shows the old value</option></select></label></div><div className="lab-actions"><button id="run-experiment" onClick={run} disabled={busy}>Run both saves</button><button id="reset-experiment" className="secondary" onClick={reset}>Reset environment</button><button className="secondary" data-view-source="model.ts">View the code</button></div><p id="experiment-status" role="status">{status}</p><div className="experiment-grid" id="results">{panels.map((p,i)=><article key={i} className="result-card" data-variant={i?'safe':'broken'}><p className="lab-eyebrow">{i?'With safeguards':'Without safeguards'}</p><h3>{p.status}</h3><div className="metric-grid">{[['Editor',p.editor.seats],['Cached summary',p.summary.seats],['Server',p.server.seats]].map(([label,value])=><div className="metric" key={label}><strong>{value}</strong><span>{label} seats</span></div>)}</div><p ref={i?error:undefined} tabIndex={i?-1:undefined}>{i&&!busy&&p.status!=='Saved on the server.'?p.status:'The editor should agree with the confirmed record.'}</p><ol className="trace">{p.events.map((e,j)=><li key={j}>{e}</li>)}</ol></article>)}</div><Keys key={'keys-'+resetKey}/><RenderDemo key={'renders-'+resetKey}/></>;
}
createRoot(document.querySelector('#experiment')!).render(<App/>);
