const app=document.querySelector('#app'),modal=document.querySelector('#modal'),modalCard=document.querySelector('#modalCard'),mapBtn=document.querySelector('#mapBtn'),crumb=document.querySelector('#crumb');
let journeyId='',activePath='',ctx=null,current=null,currentBook=null,currentLoose=null;
const esc=s=>String(s??'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
async function api(action,body=null,q=''){const r=await fetch(`api.php?action=${encodeURIComponent(action)}${q}`,{method:body?'POST':'GET',headers:body?{'Content-Type':'application/json'}:{},body:body?JSON.stringify(body):null});const x=await r.json();if(!r.ok||x.status==='error')throw new Error(x.message||'Request failed');return x}
function toast(t){const e=document.createElement('div');e.className='toast';e.textContent=t;document.body.appendChild(e);setTimeout(()=>e.remove(),1800)}
function closeModal(){modal.classList.add('hidden');modalCard.className='modal-card';modalCard.innerHTML=''}modal.onclick=e=>{if(e.target===modal)closeModal()};
function money(n){return `£${Number(n).toFixed(2)}`}
const examples=['What remains after something disappears?','Why do living things keep reaching toward what changes them?','How can many things behave like one thing?','How does a signal travel farther than the thing that made it?'];
async function home(){journeyId='';activePath='';ctx=null;current=null;currentBook=null;mapBtn.classList.add('hidden');crumb.textContent='';app.innerHTML=`<section class="landing"><div class="eyebrow">THE BEING · EXPERIMENTAL SYSTEM TEST</div><h1>What are you wondering about?</h1><p class="lead">Begin with a thought.</p><div class="testflag">EXPERIMENTAL CORPUS · NOT CANON</div><div class="thoughtbox"><textarea id="thought">${examples[0]}</textarea><button id="find">Find a thread</button></div><div class="suggestions">${examples.map(x=>`<button data-q="${esc(x)}">${esc(x)}</button>`).join('')}</div><div id="result"></div></section>`;document.querySelectorAll('[data-q]').forEach(b=>b.onclick=()=>document.querySelector('#thought').value=b.dataset.q);document.querySelector('#find').onclick=findPath}
async function findPath(){const thought=document.querySelector('#thought').value.trim();if(!thought)return;const r=await api('find_path',{thought});const box=document.querySelector('#result');if(r.status!=='found'){box.innerHTML=`<section class="path-card"><div class="kicker">NO COMPLETE THREAD</div><h2>${esc(r.message)}</h2><p>${esc(thought)}</p></section>`;box.scrollIntoView({behavior:'smooth',block:'start'});return}activePath=r.code;box.innerHTML=`<section class="path-card"><div class="kicker">THREAD FOUND · ${r.part_count} PARTS</div><h2>${esc(r.message)}</h2><p>${esc(thought)}</p><div class="path-first"><span>THE THREAD BEGINS IN</span><strong>${esc(r.first_title)}</strong></div><div class="path-nodes">${Array.from({length:r.part_count},()=>'<i></i>').join('')}</div><button id="begin" class="begin">Begin journey</button></section>`;document.querySelector('#begin').onclick=()=>begin(r.code);box.scrollIntoView({behavior:'smooth',block:'start'})}
async function begin(code){const r=await api('start',{code});journeyId=r.journey.id;activePath=code;mapBtn.classList.remove('hidden');current=r.journey.current;await openCurrent()}
async function refreshContext(){ctx=await api('context',null,`&id=${encodeURIComponent(journeyId)}`);current=ctx.current}
async function openCurrent(){await refreshContext();if(current.kind==='book')return openBook(current.id,current.page);return openLoose(current.id)}
async function openBook(id,page){currentBook=await api('book',null,`&id=${encodeURIComponent(id)}&journey=${encodeURIComponent(journeyId)}`);currentLoose=null;current={kind:'book',id,page:Number(page)};renderReader()}
async function openLoose(id){currentLoose=await api('loose',null,`&id=${encodeURIComponent(id)}`);currentBook=null;current={kind:'loose',id,page:1};renderReader()}
function pageData(){return currentBook?.pages?.find(p=>Number(p.index)===Number(current.page))}
function charCount(ps){return (ps||[]).join(' ').length}
function threadStep(){return ctx.step||{}}
function coverHtml(b){return `<article class="cover-art"><img id="pageArt" src="${esc(b.cover)}" alt=""><div class="cover-title"><div class="kicker">THE BEING · BOOK</div><h2>${esc(b.title)}</h2><p>${esc(b.subtitle)}</p></div><button id="viewImage" class="view-image">View image</button></article>`}
function artifactHtml(pg){const top=pg.text_position==='upper';return `<article class="artifact ${top?'text-top':''}"><img id="pageArt" src="${esc(pg.art)}" alt=""><div class="shade"></div><section class="story ${charCount(pg.paragraphs)>540?'dense':''}">${pg.paragraphs.map(p=>`<p>${esc(p)}</p>`).join('')}</section><button id="viewImage" class="view-image">View image</button></article>`}
function looseHtml(){return `<article class="loose"><h2>${esc(currentLoose.title)}</h2>${currentLoose.paragraphs.map(p=>`<p>${esc(p)}</p>`).join('')}</article>`}
async function renderReader(){await refreshContext();const threadMode=ctx.mode==='thread',bookMode=ctx.mode==='book';let title='',meta='',body='';if(current.kind==='book'){if(!currentBook||currentBook.id!==current.id)currentBook=await api('book',null,`&id=${encodeURIComponent(current.id)}&journey=${encodeURIComponent(journeyId)}`);title=currentBook.title;meta=threadMode?`THREAD · PAGE ${current.page} OF ${currentBook.page_count} · PART ${ctx.step_index+1} OF ${ctx.step_count}`:(current.page===0?'COMPLETE BOOK · COVER':`COMPLETE BOOK · PAGE ${current.page} OF ${currentBook.page_count}`);body=current.page===0?coverHtml(currentBook):artifactHtml(pageData())}else{title=currentLoose.title;meta=`LOOSE PAGE · PART ${ctx.step_index+1} OF ${ctx.step_count}`;body=looseHtml()}let prevDisabled=false,nextAction='page',nextLabel='Next →',prevLabel='← Previous';if(threadMode){const st=threadStep();if(current.kind==='book'){if(current.page<=st.start){if(ctx.step_index===0)prevDisabled=true;else prevLabel='← Previous part'}if(current.page>=st.end){nextAction=ctx.has_next?'advance':'finish';nextLabel=ctx.has_next?'Continue →':'Finish thread →'}}else{if(ctx.step_index===0)prevDisabled=true;else prevLabel='← Previous part';nextAction=ctx.has_next?'advance':'finish';nextLabel=ctx.has_next?'Continue →':'Finish thread →'}}else if(bookMode){if(current.page<=0)prevDisabled=true;if(current.page>=currentBook.page_count){nextAction='return';nextLabel='Return to Thread →'}}app.innerHTML=`<section class="reader"><header class="reader-head"><div><div class="meta">${esc(meta)}</div><h2>${esc(title)}</h2></div></header>${body}<div class="mode-action">${threadMode&&current.kind==='book'?'<button id="explore">Explore Book</button>':''}${bookMode?'<button id="return">↩ Return to Thread</button>':''}</div><nav class="navstrip"><button id="prev" ${prevDisabled?'disabled':''}>${prevLabel}</button><div class="state ${bookMode?'book':''}">${threadMode?'FOLLOWING THREAD':'EXPLORING BOOK'}</div><button id="next" data-action="${nextAction}">${nextLabel}</button></nav><button id="mapUnder" class="under-map">Map</button></section>`;crumb.textContent=`${title} · ${current.kind==='book'?current.page:''}`;window.scrollTo({top:0,behavior:'instant'});if(current.kind==='book'){const src=current.page===0?currentBook.cover:pageData().art;document.querySelector('#viewImage').onclick=()=>showImage(src);document.querySelector('#pageArt').onclick=()=>showImage(src)}document.querySelector('#prev').onclick=previous;document.querySelector('#next').onclick=next;document.querySelector('#mapUnder').onclick=showMap;if(document.querySelector('#explore'))document.querySelector('#explore').onclick=explore;if(document.querySelector('#return'))document.querySelector('#return').onclick=returnThread;await api('visit',{journey:journeyId,kind:current.kind,id:current.id,page:current.page})}
async function previous(){if(ctx.mode==='book'){if(current.page>0){current.page--;return renderReader()}return}const st=threadStep();if(current.kind==='book'&&current.page>st.start){current.page--;return renderReader()}if(ctx.step_index>0){const r=await api('retreat',{journey:journeyId});if(r.status==='ok'){current=r.current;return openCurrent()}}}
async function next(){const action=document.querySelector('#next').dataset.action;if(action==='advance'){const r=await api('advance',{journey:journeyId});if(r.status==='ok'){transition();current=r.current;setTimeout(openCurrent,300)}return}if(action==='finish')return finish();if(action==='return')return returnThread();if(ctx.mode==='book'){if(current.page<currentBook.page_count){current.page++;return renderReader()}}else{const st=threadStep();if(current.kind==='book'&&current.page<st.end){current.page++;return renderReader()}}}
function transition(){const t=document.querySelector('#transition'),ttl=document.querySelector('#transitionTitle');ttl.textContent='THE THREAD CONTINUES';t.classList.remove('hidden');setTimeout(()=>t.classList.add('hidden'),260)}
async function explore(){const c=await api('explore_check',{journey:journeyId});if(c.status==='owned'){modalCard.innerHTML=`<div class="kicker">EXPLORE BOOK</div><h2>${esc(c.title)}</h2><p>Start this complete Book from page 1? Your exact Thread position will remain waiting.</p><button id="goExplore" class="primary">Explore from the beginning</button><button id="stay">Stay with the Thread</button>`;modal.classList.remove('hidden');document.querySelector('#goExplore').onclick=async()=>{const r=await api('explore',{journey:journeyId});closeModal();current={kind:'book',id:r.book,page:0};currentBook=await api('book',null,`&id=${encodeURIComponent(r.book)}&journey=${encodeURIComponent(journeyId)}`);renderReader()};document.querySelector('#stay').onclick=closeModal;return}if(c.status==='purchase_required'){modalCard.innerHTML=`<div class="kicker">EXPLORE BOOK</div><h2>${esc(c.title)}</h2><p>Your Thread has shown you a small excerpt. Unlock the complete eight-page Book to begin from page 1. Your Thread position will remain waiting.</p><div class="price">${money(c.price)}</div><button id="unlock" class="primary">Unlock for this test</button><button id="cancel">Stay with the Thread</button>`;modal.classList.remove('hidden');document.querySelector('#unlock').onclick=async()=>{await api('unlock',{book:c.book});const r=await api('explore',{journey:journeyId});closeModal();current={kind:'book',id:r.book,page:0};currentBook=await api('book',null,`&id=${encodeURIComponent(r.book)}&journey=${encodeURIComponent(journeyId)}`);renderReader()};document.querySelector('#cancel').onclick=closeModal}}
async function returnThread(){const r=await api('return_thread',{journey:journeyId});if(r.status==='ok'){current=r.current;return openCurrent()}}
function showImage(src){modalCard.innerHTML=`<img class="fullimg" src="${esc(src)}" alt=""><button id="closeImg">Return to reading</button>`;modal.classList.remove('hidden');document.querySelector('#closeImg').onclick=closeModal}
async function showMap(){if(!journeyId)return toast('Begin a thread first.');const m=await api('map',null,`&id=${encodeURIComponent(journeyId)}`);modalCard.className='modal-card map-card';modalCard.innerHTML=`<div class="kicker">YOUR JOURNEY</div><h2>Map</h2><p>Outlined pages are the excerpt selected for this Thread. Future parts stay unnamed until reached.</p>${m.nodes.map((n,i)=>{if(!n.reached)return `<button class="map-node unreached" disabled><strong>Unknown</strong><span>PART ${i+1}</span></button>`;if(n.kind==='loose')return `<button class="map-node ${n.current?'current':''}" disabled><strong>Loose Page</strong><span>CONNECTIVE MATERIAL · PART ${i+1}</span></button>`;return `<button class="map-node ${n.current?'current':''}" disabled><strong>${esc(n.title)}</strong><span>${n.owned?'OWNED':'TOUCHED'} · THREAD PAGES ${n.start}–${n.end}</span><div class="map-pages">${Array.from({length:n.page_count},(_,j)=>`<i class="${j+1>=n.start&&j+1<=n.end?'window':''}">${j+1}</i>`).join('')}</div></button>`}).join('')}<button id="mapClose">Return to reading</button>`;modal.classList.remove('hidden');document.querySelector('#mapClose').onclick=closeModal}
async function showLibrary(){const r=await api('library');modalCard.innerHTML=`<div class="kicker">MY LIBRARY</div><h2>Books you've touched</h2><div class="library-list">${r.books.length?r.books.map(b=>`<button data-book="${b.id}" data-owned="${b.owned?'1':'0'}"><img src="${esc(b.cover)}"><span><strong>${esc(b.title)}</strong><small>${b.read?'READ':b.owned?'OWNED':'TOUCHED'}</small></span><b>${b.owned?'Open':money(b.price)}</b></button>`).join(''):'<p>No Books touched yet.</p>'}</div><button id="libClose">Close</button>`;modal.classList.remove('hidden');document.querySelector('#libClose').onclick=closeModal;modalCard.querySelectorAll('[data-book]').forEach(b=>b.onclick=async()=>{const id=b.dataset.book;if(b.dataset.owned==='1'){closeModal();journeyId='';mapBtn.classList.add('hidden');currentBook=await api('book',null,`&id=${encodeURIComponent(id)}`);current={kind:'book',id,page:0};renderStandaloneBook()}else toast('Unlock it from a Thread excerpt first.')})}
function renderStandaloneBook(){const pg=currentBook.pages.find(p=>p.index===current.page);const isCover=current.page===0;app.innerHTML=`<section class="reader"><header class="reader-head"><div><div class="meta">YOUR LIBRARY · COMPLETE BOOK · ${isCover?'COVER':`PAGE ${current.page} OF ${currentBook.page_count}`}</div><h2>${esc(currentBook.title)}</h2></div></header>${isCover?coverHtml(currentBook):artifactHtml(pg)}<nav class="navstrip"><button id="prev" ${current.page<=0?'disabled':''}>← Previous</button><div class="state book">READING BOOK</div><button id="next" ${current.page>=currentBook.page_count?'disabled':''}>Next →</button></nav></section>`;crumb.textContent=currentBook.title;document.querySelector('#prev').onclick=()=>{if(current.page>0){current.page--;renderStandaloneBook()}};document.querySelector('#next').onclick=()=>{if(current.page<currentBook.page_count){current.page++;renderStandaloneBook()}};{const src=isCover?currentBook.cover:pg.art;document.querySelector('#viewImage').onclick=()=>showImage(src);document.querySelector('#pageArt').onclick=()=>showImage(src)}}
async function finish(){mapBtn.classList.add('hidden');const pulses=await api('pulses',null,`&code=${encodeURIComponent(activePath)}`);app.innerHTML=`<section class="end"><div class="kicker">YOU REACHED THE END OF THIS THREAD</div><h1>What stayed with you?</h1><p>Leave one Pulse, or leave nothing.</p><textarea id="pulse" maxlength="360" placeholder="Something that stayed with you…"></textarea><div><button id="leave">Leave a Pulse</button><button id="nothing">Leave nothing</button></div><div class="pulses"><div class="kicker">PULSES</div>${pulses.items.map(p=>`<div class="pulse">${esc(p.text)}</div>`).join('')}</div><button id="again">Begin another thought</button></section>`;crumb.textContent='End of thread';document.querySelector('#leave').onclick=async()=>{const t=document.querySelector('#pulse').value;if(!t.trim())return;await api('pulse',{code:activePath,text:t});finish()};document.querySelector('#nothing').onclick=home;document.querySelector('#again').onclick=home}
document.querySelector('#homeBtn').onclick=home;document.querySelector('#libBtn').onclick=showLibrary;mapBtn.onclick=showMap;home().catch(e=>app.innerHTML=`<div class="fatal">${esc(e.message)}</div>`);
