/* global React, I, Logo */
const portalLabels = { draft: 'Draft', review: 'For your review', approved: 'Approved', changes_requested: 'Changes requested' };
async function portalApi(op, body, query = {}, signal, headers = {}) {
  const params = new URLSearchParams({ op, ...query });
  const response = await fetch(`/api/portal?${params}`, { credentials: 'same-origin', signal,
    ...(body !== undefined ? { method: 'POST', headers: { 'Content-Type': 'application/json', ...headers }, body: JSON.stringify(body) } : {}) });
  let data;
  try { data = await response.json(); } catch { throw new Error('The workspace is unavailable. Please try again shortly.'); }
  if (!response.ok) { const error = new Error(data.error || 'The request could not be completed.'); error.status = response.status; throw error; }
  return data;
}
function portalDate(value) { return new Intl.DateTimeFormat('en-US', { month: 'short', day: 'numeric' }).format(new Date(value)); }
function PortalMedia({document,url}){
  const [zoom,setZoom]=React.useState(false);
  if(!document.version.hasFile)return null;
  const inline=`${url}&inline=1`;
  return <div className={`portal-media-viewer ${zoom?'media-zoomed':''}`}><div className="portal-media-tools"><span>{document.kind==='pdf'?`PDF document${document.version.previewPages?' · '+document.version.previewPages+' preview pages':''}`:'Image preview'}</span>{document.kind==='image'&&<button className="portal-secondary" onClick={()=>setZoom(v=>!v)}>{zoom?'Fit image':'View full size'}</button>}<a className="portal-secondary" href={inline} target="_blank" rel="noopener noreferrer">Open original ↗</a></div>{document.kind==='image'?<div className="portal-image-stage"><img src={inline} alt={document.title}/></div>:document.version.previewPages?<div className="portal-pdf-pages">{Array.from({length:document.version.previewPages},(_,i)=><figure key={i}><img loading="lazy" src={`/api/portal?op=preview-page&id=${encodeURIComponent(document.id)}&version=${encodeURIComponent(document.version.id)}&page=${i+1}`} alt={`${document.title} · page ${i+1}`}/><figcaption>Page {i+1}</figcaption></figure>)}</div>:<div className="portal-library-empty"><h3>Read the original PDF</h3><p>A page preview has not been prepared for this file. Use Open original or Download.</p></div>}<p className="portal-media-help">{document.kind==='pdf'?'Page images are read-only previews. Open the original for selectable text and the complete PDF.':'Original file, shown without cropping. Use full size to inspect the details.'}</p></div>;
}
function PortalText({ body }) {
  // Render text as React elements. Generated HTML and scripts never execute here.
  const lines = (body || '').split('\n'), nodes = [];
  const inline = value => value.split(/(\*\*[^*]+\*\*|`[^`]+`|\[[^\]]+\]\(https?:\/\/[^\s)]+\))/g).map((part, i) => {
    if (part.startsWith('**')) return <strong key={i}>{part.slice(2, -2)}</strong>;
    if (part.startsWith('`')) return <code key={i}>{part.slice(1, -1)}</code>;
    const link = part.match(/^\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)$/);
    if (link) return <a key={i} href={link[2]} target="_blank" rel="noopener noreferrer">{link[1]}</a>;
    return part;
  });
  const cells = line => line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|').map(s => s.trim());
  for (let i = 0; i < lines.length; i++) {
    const line = lines[i], key = i;
    if (line.startsWith('```')) {
      const block = []; while (++i < lines.length && !lines[i].startsWith('```')) block.push(lines[i]);
      nodes.push(<pre key={key}>{block.join('\n')}</pre>); continue;
    }
    if (line.includes('|') && lines[i + 1] && /^\|?\s*:?-{3,}/.test(lines[i + 1])) {
      const headers = cells(line), rows = []; i++;
      while (i + 1 < lines.length && lines[i + 1].includes('|') && lines[i + 1].trim()) rows.push(cells(lines[++i]));
      nodes.push(<div className="portal-table" key={key}><table><thead><tr>{headers.map((h, n) => <th key={n}>{inline(h)}</th>)}</tr></thead><tbody>{rows.map((r, n) => <tr key={n}>{headers.map((_, c) => <td key={c}>{inline(r[c] || '')}</td>)}</tr>)}</tbody></table></div>); continue;
    }
    if (/^[-*] /.test(line) || /^\d+\. /.test(line)) {
      const ordered = /^\d+\. /.test(line), entries = [line.replace(/^(?:[-*]|\d+\.) /, '')];
      while (i + 1 < lines.length && (ordered ? /^\d+\. / : /^[-*] /).test(lines[i + 1])) entries.push(lines[++i].replace(/^(?:[-*]|\d+\.) /, ''));
      const Tag = ordered ? 'ol' : 'ul'; nodes.push(<Tag key={key}>{entries.map((s, n) => <li key={n}>{inline(s)}</li>)}</Tag>); continue;
    }
    if (line.startsWith('### ')) nodes.push(<h3 key={i}>{inline(line.slice(4))}</h3>);
    else if (line.startsWith('## ')) nodes.push(<h2 key={i}>{inline(line.slice(3))}</h2>);
    else if (line.startsWith('# ')) nodes.push(<h1 key={i}>{inline(line.slice(2))}</h1>);
    else if (line.startsWith('> ')) nodes.push(<blockquote key={i}>{inline(line.slice(2))}</blockquote>);
    else if (!line.trim()) nodes.push(<div className="portal-text-gap" key={i} />);
    else nodes.push(<p key={i}>{inline(line)}</p>);
  }
  return <div className="portal-document">{nodes}</div>;
}
function PortalLogin({ onLogin, initialError }) {
  const [address, setAddress] = React.useState('');
  const [password, setPassword] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState(initialError || '');
  const [invitation] = React.useState(() => {
    const value = new URLSearchParams(window.location.hash.slice(1)).get('invite');
    if (value) window.history.replaceState({}, '', window.location.pathname);
    return value;
  });
  async function submit(e) {
    e.preventDefault(); setBusy(true); setError('');
    try { const data = await portalApi(invitation ? 'redeem' : 'login', invitation ? { token: invitation, password } : { email: address, password }); onLogin(data.user); }
    catch (e) { setError(e.message); } finally { setBusy(false); }
  }
  return <main className="portal-entry"><div className="portal-entry-story">
    <div className="portal-kicker"><Logo size={46}/> YOUR ELEVEN VIEWS WORKSPACE</div>
    <h1>The work.<br />The conversation.<br /><em>All here.</em></h1>
    <p>Your plans, creative and finished documents, with a place to make the next version together.</p>
    <div className="portal-entry-proof"><span>01 / RECEIVE</span><span>02 / REVIEW</span><span>03 / KEEP</span></div>
    <div className="portal-entry-paper" aria-hidden="true"><span>SHARED BY ELEVEN VIEWS</span><h3>A little clarity.<br />A lot of possibility.</h3><div /><div /><div /><b>Ready for your review ↗</b></div>
  </div><section className="portal-entry-form"><span className="portal-kicker">CLIENT ACCESS</span>
    <h2>{invitation ? 'Make yourself at home.' : 'Welcome back.'}</h2>
    <p>{invitation ? 'Choose a password to accept your workspace invitation.' : 'Sign in with the email invited by your studio contact.'}</p>
    <form onSubmit={submit}>
      {!invitation && <label>Email address<input autoComplete="username" required type="email" value={address} onChange={e => setAddress(e.target.value)} placeholder="you@organization.org" /></label>}
      <label>{invitation ? 'Choose a password' : 'Password'}<input required autoComplete={invitation ? 'new-password' : 'current-password'} type="password" minLength={invitation ? 12 : undefined} maxLength={128} value={password} onChange={e => setPassword(e.target.value)} /></label>
      {invitation && <small>Use at least 12 characters.</small>}
      {error && <div className="portal-alert" role="alert">{error}</div>}
      <button className="portal-primary" disabled={busy}>{busy ? 'Opening your workspace…' : invitation ? 'Accept invitation' : 'Open your workspace'} <I name="arrow" size={16} /></button>
    </form><p className="portal-entry-help">Have a public Eleven Views account? <a href="/login">Sign in here</a>.<br/>Need invited access or a password reset? <a href="/contact">Contact Eleven Views</a>.</p>
  </section></main>;
}
function PortalCompose({ clientId, user, editing, onClose, onSaved }) {
  const [title, setTitle] = React.useState(editing?.title || '');
  const [summary, setSummary] = React.useState(editing?.summary || '');
  const [body, setBody] = React.useState(editing?.version.body || '');
  const [file, setFile] = React.useState(null);
  const [busy, setBusy] = React.useState(false), [error, setError] = React.useState('');
  const titleRef = React.useRef(null);
  React.useEffect(() => {
    const previous = window.document.activeElement;
    titleRef.current?.focus();
    const close = e => {
      if (e.key === 'Escape') onClose();
      if (e.key === 'Tab') {
        const controls = [...window.document.querySelectorAll('.portal-modal button:not(:disabled),.portal-modal input,.portal-modal textarea')];
        const first = controls[0], last = controls[controls.length - 1];
        if (e.shiftKey && window.document.activeElement === first) { e.preventDefault(); last?.focus(); }
        else if (!e.shiftKey && window.document.activeElement === last) { e.preventDefault(); first?.focus(); }
      }
    };
    window.document.addEventListener('keydown', close);
    return () => { window.document.removeEventListener('keydown', close); previous?.focus(); };
  }, []);
  async function save(e) {
    e.preventDefault(); setBusy(true); setError('');
    try {
      let upload;
      if (file) {
        if (file.size > 2 * 1024 * 1024) throw new Error('Choose a file smaller than 2 MB.');
        const bytes = new Uint8Array(await file.arrayBuffer());
        let binary = ''; for (const b of bytes) binary += String.fromCharCode(b);
        upload = { name: file.name, mime: file.type, base64: btoa(binary) };
      }
      const op = editing ? 'version-create' : user.role === 'operator' ? 'artifact-create' : 'save-draft';
      const saved = await portalApi(op, { clientId, title, summary, body, file: upload, ...(editing ? { id: editing.id, expectedVersionId: editing.version.id } : {}) });
      onSaved(saved); onClose();
    } catch (e) { setError(e.message); } finally { setBusy(false); }
  }
  return <div className="portal-modal-backdrop"><section className="portal-modal" role="dialog" aria-modal="true" aria-labelledby="compose-title">
    <div className="portal-modal-heading"><div><span className="portal-kicker">YOUR WORKSPACE</span><h2 id="compose-title">{editing ? 'Make the next version.' : 'Start something.'}</h2></div><button aria-label="Close editor" className="portal-icon" onClick={onClose}><I name="close" /></button></div>
    <form onSubmit={save}>
      <label>Document title<input ref={titleRef} required maxLength={160} readOnly={Boolean(editing)} value={title} onChange={e => setTitle(e.target.value)} placeholder="A campaign, an idea, a brief" /></label>
      {!editing && <label>A short introduction<input maxLength={400} value={summary} onChange={e => setSummary(e.target.value)} placeholder="What should the reader know?" /></label>}
      <label>Document<textarea rows={10} maxLength={200000} value={body} onChange={e => setBody(e.target.value)} placeholder={'# Your title\n\nWrite your brief or paste your document here.'} /></label>
      {!editing && <label className="portal-upload">Attach a PDF or image <span>PNG, JPEG, WebP or PDF, up to 2 MB</span><span className="portal-file-picker"><I name="plus" size={15}/><b>{file ? file.name : 'Choose a file'}</b></span><input className="sr-only" type="file" accept="application/pdf,image/png,image/jpeg,image/webp" onChange={e => setFile(e.target.files[0] || null)} /></label>}
      {error && <p className="portal-alert" role="alert">{error}</p>}
      <div className="portal-modal-actions"><small>Your draft will be saved in this workspace. You can keep working on it at any time.</small><button className="portal-primary" disabled={busy || (!body.trim() && !file)}>{busy ? 'Saving…' : 'Save draft'}</button></div>
    </form>
  </section></div>;
}
function artifactIcon(kind){return kind==='image'?'image':kind==='pdf'?'fileText':kind==='video'?'video':kind==='ad_pack'?'grid':'fileText';}
function PortalArtifactCard({artifact,onOpen,onRevise,onRefine}){
  if(!artifact)return null;
  const ready=artifact.status==='ready',preview=artifact.previewUrl;
  return <article className={`portal-inline-artifact artifact-${artifact.kind}`} aria-label={`${artifact.title}, ${artifact.status}`}>
    <div className="portal-inline-artifact-preview">{artifact.kind==='image'&&ready&&preview?<img src={preview} alt={artifact.title}/>:<div className="portal-inline-artifact-symbol"><I name={artifactIcon(artifact.kind)} size={30}/><span>{artifact.mimeType||artifact.kind}</span></div>}</div>
    <div className="portal-inline-artifact-body"><div className="portal-inline-artifact-heading"><span><I name={artifactIcon(artifact.kind)} size={15}/>{artifact.kind==='image'?'Creative':artifact.kind==='pdf'?'PDF':'Document'}</span><span className={`artifact-state state-${artifact.status}`}>{artifact.status==='creating'?'Creating':artifact.status==='preparing'?'Preparing':artifact.status==='failed'?'Failed':'Ready'}</span></div><h4>{artifact.title}</h4>{artifact.excerpt&&artifact.kind!=='image'&&<p>{artifact.excerpt.replace(/^#+\s*/,'').slice(0,180)}</p>}<small>Version {artifact.version} · {portalDate(artifact.createdAt)}</small>
      <div className="portal-inline-artifact-actions">{onOpen?<button onClick={()=>onOpen(artifact)} disabled={!ready}><I name="eye" size={14}/> Preview</button>:<a href={artifact.openUrl}><I name="eye" size={14}/> Open</a>}{onRefine&&artifact.kind==='image'&&ready&&<button onClick={()=>onRefine(artifact)}><I name="pencil" size={14}/> Refine in chat</button>}<a href={artifact.downloadUrl} aria-label={`Download ${artifact.title}`}><I name="download" size={14}/> Download</a>{onRevise&&artifact.canRevise&&<button onClick={()=>onRevise(artifact)}><I name="pencil" size={14}/> Revise</button>}</div>
    </div>
  </article>;
}
function PortalArtifactGrid({artifacts,onOpen}){return <div className="portal-artifact-grid">{artifacts.map(artifact=><PortalArtifactCard key={artifact.id} artifact={artifact} onOpen={onOpen}/>)}</div>;}
function PortalProgress({part}){return <div className={`portal-generation-progress progress-${part.status}`} role="status"><I name={part.status==='failed'?'alertCircle':part.status==='ready'?'checkCircle':'loader'} size={16}/><span>{part.label}</span></div>;}
function PortalImagePreview({artifact,onClose,onRefine}){
  React.useEffect(()=>{const close=e=>{if(e.key==='Escape')onClose();};window.document.addEventListener('keydown',close);return()=>window.document.removeEventListener('keydown',close);},[onClose]);
  return <div className="portal-image-preview-backdrop" onMouseDown={e=>{if(e.target===e.currentTarget)onClose();}}><section className="portal-image-preview-dialog" role="dialog" aria-modal="true" aria-label={`Preview ${artifact.title}`}><header><div><span className="portal-kicker">PROJECT IMAGE</span><h3>{artifact.title}</h3></div><button className="portal-icon" onClick={onClose} aria-label="Close image preview"><I name="close" size={17}/></button></header><div><img src={artifact.previewUrl} alt={artifact.title}/></div><footer><button className="portal-primary" onClick={()=>{onRefine(artifact);onClose();}}><I name="pencil" size={14}/> Keep editing in chat</button><a className="portal-secondary" href={artifact.downloadUrl}><I name="download" size={14}/> Download</a></footer></section></div>;
}
async function portalFilePayload(file){
  if(file.size>8*1024*1024)throw new Error('Choose a file smaller than 8 MB.');
  const bytes=new Uint8Array(await file.arrayBuffer());let binary='';for(const byte of bytes)binary+=String.fromCharCode(byte);
  return {name:file.name,mime:file.type,base64:btoa(binary)};
}
function LegacyPortalAssistant({ document, user, onSaved, starterPrompt='' }) {
  const [ready,setReady]=React.useState(Boolean(window.puter)),[connected,setConnected]=React.useState(false);
  const [question,setQuestion]=React.useState(''),[messages,setMessages]=React.useState([]),[models,setModels]=React.useState([]),[model,setModel]=React.useState('');
  const [mode,setMode]=React.useState('document'),[created,setCreated]=React.useState(null);
  const [busy,setBusy]=React.useState(false),[connecting,setConnecting]=React.useState(false),[saving,setSaving]=React.useState(false),[error,setError]=React.useState(''),[notice,setNotice]=React.useState('');
  const [allowContext,setAllowContext]=React.useState(false),[loading,setLoading]=React.useState(true),[retry,setRetry]=React.useState(0),[sdkRetry,setSdkRetry]=React.useState(0);
  const abort=React.useRef(null),generation=React.useRef(0),running=React.useRef(false),transcript=React.useRef(null),nearBottom=React.useRef(true),composer=React.useRef(null);
  const scope={artifactId:document.id,versionId:document.version.id};
  React.useEffect(()=>{
    if(window.puter){setReady(true);return;}
    let script=window.document.querySelector('script[data-ev-puter]');
    if(script?.dataset.failed==='true'){script.remove();script=null;}
    if(!script){script=window.document.createElement('script');script.src='https://js.puter.com/v2/';script.dataset.evPuter='true';script.async=true;window.document.head.appendChild(script);}
    const loaded=()=>setReady(Boolean(window.puter)),failed=()=>{script.dataset.failed='true';setError('Puter could not load. Your documents and saved chat remain available.');};
    script.addEventListener('load',loaded);script.addEventListener('error',failed);
    const timer=setTimeout(()=>{if(!window.puter)failed();},15000);
    return()=>{clearTimeout(timer);script.removeEventListener('load',loaded);script.removeEventListener('error',failed);};
  },[sdkRetry]);
  React.useEffect(()=>{
    const run=++generation.current;abort.current?.abort();running.current=false;setBusy(false);setMessages([]);setCreated(null);setQuestion(starterPrompt);setAllowContext(false);setError('');setNotice('');setLoading(true);nearBottom.current=true;
    const controller=new AbortController();
    portalApi('chat',undefined,scope,controller.signal).then(data=>{if(run===generation.current)setMessages(data.messages);}).catch(e=>{if(run===generation.current&&e.name!=='AbortError')setError(e.message);}).finally(()=>{if(run===generation.current)setLoading(false);});
    return()=>{generation.current++;abort.current?.abort();controller.abort();running.current=false;};
  },[document.id,document.version.id,retry,starterPrompt]);
  React.useEffect(()=>{if(transcript.current&&nearBottom.current)transcript.current.scrollTop=transcript.current.scrollHeight;},[messages,busy]);
  async function connect(){
    if(connecting)return;setConnecting(true);setError('');
    try{
      if(!await window.puter.auth.isSignedIn())await window.puter.auth.signIn();
      if(!await window.puter.auth.isSignedIn())throw new Error('Not connected');
      setConnected(true);
      try{const available=await window.puter.ai.listModels();const catalog=Array.isArray(available)?available.filter(m=>typeof m.id==='string').sort((a,b)=>(a.name||a.id).localeCompare(b.name||b.id)):[];setModels(catalog);const recommended=window.EVPortalChat.qualityModel(catalog);setModel(recommended);setNotice(recommended?'Quality model selected. Requests use your Puter allowance.':'Choose a model from the catalog. Atlas will not silently use Puter’s small default.');}
      catch{setError('The model catalog could not load. Reconnect to choose a quality model before sending.');setConnected(false);}
    }catch{setError('Puter connection was not completed. You can reconnect when you’re ready.');}finally{setConnecting(false);}
  }
  const persist=message=>portalApi('chat-message',{...scope,message:{id:message.id,role:message.role,content:message.content,status:message.status,model:message.model||''}});
  async function ask(prompt,retryId){
    prompt=prompt.trim();if(!connected||loading||running.current||saving||!prompt)return;if(!model){setError('Choose a model before sending.');return;}
    running.current=true;const run=++generation.current;
    const prior=retryId?messages.slice(0,messages.findIndex(m=>m.id===retryId)-1):messages;
    const existingUser=retryId?messages[messages.findIndex(m=>m.id===retryId)-1]:null;
    const sent=existingUser||{id:crypto.randomUUID(),role:'user',content:prompt,status:'complete',model:''};
    const reply={id:retryId||crypto.randomUUID(),role:'assistant',content:'',status:'streaming',model};
    const base=[...prior,sent];setMessages([...base,reply]);setQuestion('');if(composer.current)composer.current.style.height='auto';setBusy(true);setError('');setNotice('');nearBottom.current=true;
    const controller=new AbortController();abort.current=controller;
    const timer=setTimeout(()=>controller.abort(new DOMException('Response timed out','TimeoutError')),60000);
    let output='',status='complete',userSaved=false;
    try{
      await persist(sent);
      userSaved=true;
      if(run!==generation.current)return;
      if(controller.signal.aborted)throw controller.signal.reason || new DOMException('Stopped','AbortError');
      const result=await window.puter.ai.chat(window.EVPortalChat.messagesFor(document,prior,allowContext,prompt,mode),{model,normalize:true,stream:true,max_tokens:4000,signal:controller.signal});
      output=await window.EVPortalChat.consume(result,{signal:controller.signal,onText:content=>{output=content;if(run===generation.current)setMessages([...base,{...reply,content}]);}});
    }catch(e){
      status=e.name==='AbortError'?'stopped':'error';
      if(run===generation.current&&status==='error')setError(/fund|credit|allowance|402/i.test(`${e.message} ${e.code}`)?'Your Puter allowance is used up. Your saved work is still available.':e.name==='TimeoutError'?'The response timed out. You can retry when you’re ready.':'The response could not finish. Your previous messages are still here. Try again or choose another model.');
    }finally{
      clearTimeout(timer);
      if(run===generation.current){
        const completed={...reply,content:output,status};setMessages(userSaved?[...base,completed]:prior);
        if(userSaved){try{await persist(completed);}catch(e){setError(`This reply could not be saved: ${e.message} Copy any useful text before leaving.`);}}
        else{setQuestion(prompt);setError('Your message could not be saved. Nothing was sent to the model. Please try again.');}
        if(userSaved&&status==='complete'&&mode==='document'){try{const draft=await storeDraft(completed);if(run===generation.current){setCreated(draft);setNotice('Document saved to your library as a draft. Review it before sharing.');}}catch(e){if(run===generation.current)setError(`The response is here, but its document could not be saved: ${e.message} Use Save as draft to retry.`);}}
        if(run===generation.current){setBusy(false);running.current=false;}
      }
    }
  }
  async function storeDraft(message){const draft=window.EVPortalChat.draftFor(message.content);return portalApi(user.role==='operator'?'artifact-create':'save-draft',{clientId:document.clientId,...draft,summary:`Atlas-generated draft. Model: ${message.model||model}. Requires review.`},{},undefined,{'Idempotency-Key':`atlas-document-${message.id}`});}
  async function saveDraft(message){
    setSaving(true);setError('');
    try{const saved=await storeDraft(message);onSaved(saved);}
    catch(e){setError(e.message);}finally{setSaving(false);}
  }
  async function copy(message){try{await navigator.clipboard.writeText(message.content);setNotice('Response copied.');}catch{setError('Copy is unavailable. Select the response text to copy it.');}}
  const last=messages[messages.length-1];
  return <section className="portal-assistant portal-chat" aria-label="Atlas conversation">
    <header className="portal-chat-header"><div><span className="portal-chat-brand"><I name="spark" size={16}/> Atlas</span><span className="portal-chat-private">Private to you · this document version</span></div><label className="portal-chat-model"><span className="sr-only">AI model</span><select aria-label="AI model" value={model} disabled={!connected||busy||saving} onChange={e=>setModel(e.target.value)}><option value="">Choose a quality model</option>{models.map(m=><option key={`${m.provider}:${m.id}`} value={m.id}>{m.name||m.id} · {m.provider}</option>)}</select></label></header>
    <div className="portal-output-mode" role="group" aria-label="Atlas output"><button aria-pressed={mode==='document'} disabled={busy} onClick={()=>setMode('document')}>Create document</button><button aria-pressed={mode==='chat'} disabled={busy} onClick={()=>setMode('chat')}>Chat</button><small>{mode==='document'?'Creates a saved library draft, not just a reply.':'Discuss and explore. Save useful replies when you choose.'}</small></div>
    {created&&<div className="portal-created-artifact"><I name="check" size={18}/><div><strong>{created.title}</strong><small>Saved document · draft · review required</small></div><button className="portal-secondary" onClick={()=>onSaved(created)}>Open document ↗</button></div>}
    <div ref={transcript} className="portal-chat-transcript" role="log" aria-label="Conversation history" aria-live="polite" aria-relevant="additions" onScroll={e=>{const n=e.currentTarget;nearBottom.current=n.scrollHeight-n.scrollTop-n.clientHeight<90;}}>
      {loading?<p className="portal-muted" role="status">Opening your conversation…</p>:messages.length===0?<div className="portal-chat-welcome"><span className="portal-chat-emblem"><I name="spark" size={23}/></span><h3>What shall we work on?</h3><p>Think it through. Find the words.<br/>Turn a first thought into a useful draft.</p>{!connected&&<button className="portal-secondary" disabled={!ready||connecting} onClick={connect}>{connecting?'Connecting…':ready?'Connect Puter':'Preparing connection…'}<I name="ext" size={13}/></button>}<small>Your Puter account powers AI usage. Saved messages stay private to your Eleven Views account.</small></div>:messages.map((m,index)=><article key={m.id} className={`portal-chat-message chat-${m.role}`}><div className="portal-chat-message-label">{m.role==='user'?'You':'Atlas'}{m.role==='assistant'&&m.model&&<span>{m.model}</span>}</div>{m.content?<PortalText body={m.content}/>:<p className="portal-muted">{busy&&index===messages.length-1?'Thinking…':m.status==='stopped'?'Response stopped.':'No response was returned.'}</p>}{m.role==='assistant'&&m.status!=='streaming'&&<div className="portal-chat-message-actions">{m.content&&<><button type="button" onClick={()=>copy(m)}>Copy</button><button type="button" disabled={busy||saving} onClick={()=>saveDraft(m)}>Save as draft</button></>}{m.status!=='complete'&&<span>{m.status==='stopped'?'Stopped':'Incomplete'}</span>}{m===last&&index>0&&<button type="button" disabled={busy||saving||!connected} onClick={()=>ask(messages[index-1].content,m.id)}>Retry</button>}</div>}</article>)}
    </div>
    {!connected&&messages.length>0&&<button className="portal-secondary portal-chat-reconnect" disabled={!ready||connecting} onClick={connect}>{connecting?'Connecting…':'Connect Puter to continue'}</button>}
    {error&&<div className="portal-alert" role="alert">{error}<button className="portal-text-button" onClick={()=>{setRetry(n=>n+1);if(!ready)setSdkRetry(n=>n+1);}}>Reload chat</button></div>}{notice&&<p className="portal-chat-notice" role="status">{notice}</p>}
    {messages.length===0&&connected&&!loading&&<div className="portal-chat-prompts">{['Help me plan my next step.','Draft three social captions.'].map(p=><button key={p} disabled={busy} onClick={()=>ask(p)}>{p}<span>↗</span></button>)}</div>}
    <div className="portal-chat-composer-wrap"><label className="portal-chat-context"><input type="checkbox" checked={allowContext} disabled={busy||!document.version.body} onChange={e=>setAllowContext(e.target.checked)}/><span>Include “{document.title}” · v{document.version.number}</span></label>
      <form className="portal-chat-composer" onSubmit={e=>{e.preventDefault();ask(question);}}><label className="sr-only" htmlFor="portal-question">Message Atlas</label><textarea ref={composer} id="portal-question" value={question} maxLength={4000} rows={2} disabled={!connected||loading||saving} placeholder={connected?'Message Atlas…':'Connect Puter to message Atlas…'} onChange={e=>setQuestion(e.target.value)} onInput={e=>{e.currentTarget.style.height='auto';e.currentTarget.style.height=`${Math.min(e.currentTarget.scrollHeight,160)}px`;}} onKeyDown={e=>{if(e.key==='Enter'&&!e.shiftKey&&!e.nativeEvent.isComposing){e.preventDefault();e.currentTarget.form.requestSubmit();}}}/><div className="portal-chat-composer-bottom"><span>Enter to send · Shift + Enter for a new line</span>{busy?<button type="button" className="portal-chat-send portal-chat-stop" aria-label="Stop response" onClick={()=>abort.current?.abort()}><span/></button>:<button type="submit" className="portal-chat-send" aria-label="Send message" disabled={!connected||loading||saving||!question.trim()}><I name="arrowUp" size={17}/></button>}</div></form>
      <p className="portal-chat-disclaimer">AI can make mistakes. Review before using. Model requests draw from your Puter allowance or credits under your account terms. Previous chat turns may be sent with your next message. The checkbox includes written document text only, not image or PDF attachments.</p><span className="sr-only" role="status">{busy?'Atlas is responding.':'Atlas is ready.'}</span>
    </div>
  </section>;
}
function PortalAssistant({document,user,project,projects=[],onSaved,onAttachProject,onCreateProject,starterPrompt=''}){
  const [ready,setReady]=React.useState(Boolean(window.puter)),[connected,setConnected]=React.useState(false),[connecting,setConnecting]=React.useState(false);
  const [puterUser,setPuterUser]=React.useState(null);
  const [question,setQuestion]=React.useState(''),[messages,setMessages]=React.useState([]),[models,setModels]=React.useState([]),[mode,setMode]=React.useState('automatic');
  const [action,setAction]=React.useState(''),[attachment,setAttachment]=React.useState(null),[imageReference,setImageReference]=React.useState(null),[previewArtifact,setPreviewArtifact]=React.useState(null),[routeInfo,setRouteInfo]=React.useState(null),[details,setDetails]=React.useState(false);
  const [busy,setBusy]=React.useState(false),[saving,setSaving]=React.useState(false),[loading,setLoading]=React.useState(true),[error,setError]=React.useState(''),[notice,setNotice]=React.useState('');
  const [allowContext,setAllowContext]=React.useState(Boolean(project)),[retry,setRetry]=React.useState(0),[sdkRetry,setSdkRetry]=React.useState(0);
  const abort=React.useRef(null),generation=React.useRef(0),running=React.useRef(false),transcript=React.useRef(null),nearBottom=React.useRef(true),composer=React.useRef(null),fileInput=React.useRef(null);
  const scope={artifactId:document.id,versionId:document.version.id,...(project?{projectId:project.id}:{})};
  React.useEffect(()=>{
    if(window.puter){setReady(true);return;}
    let script=window.document.querySelector('script[data-ev-puter]');if(script?.dataset.failed==='true'){script.remove();script=null;}
    if(!script){script=window.document.createElement('script');script.src='https://js.puter.com/v2/';script.dataset.evPuter='true';script.async=true;window.document.head.appendChild(script);}
    const loaded=()=>setReady(Boolean(window.puter)),failed=()=>{script.dataset.failed='true';setError('Puter could not load. Your projects, files and saved chat are still available.');};
    script.addEventListener('load',loaded);script.addEventListener('error',failed);const timer=setTimeout(()=>{if(!window.puter)failed();},15000);
    return()=>{clearTimeout(timer);script.removeEventListener('load',loaded);script.removeEventListener('error',failed);};
  },[sdkRetry]);
  React.useEffect(()=>{
    const run=++generation.current;abort.current?.abort();running.current=false;setBusy(false);setMessages([]);setQuestion(starterPrompt);setImageReference(null);setPreviewArtifact(null);setError('');setNotice('');setLoading(true);nearBottom.current=true;
    const controller=new AbortController();portalApi('chat',undefined,scope,controller.signal).then(data=>{if(run===generation.current)setMessages(data.messages);}).catch(e=>{if(run===generation.current&&e.name!=='AbortError')setError(e.message);}).finally(()=>{if(run===generation.current)setLoading(false);});
    return()=>{generation.current++;abort.current?.abort();controller.abort();running.current=false;};
  },[document.id,document.version.id,project?.id,retry,starterPrompt]);
  React.useEffect(()=>{if(transcript.current&&nearBottom.current)transcript.current.scrollTop=transcript.current.scrollHeight;},[messages,busy]);
  async function connect(switchAccount=false){
    if(connecting||!window.puter)return;setConnecting(true);setError('');
    try{if(switchAccount)await window.puter.auth.signIn({request_auth:true});else if(!await window.puter.auth.isSignedIn())await window.puter.auth.signIn();if(!await window.puter.auth.isSignedIn())throw new Error('Not connected');
      const available=await window.puter.ai.listModels();const catalog=Array.isArray(available)?available.filter(item=>typeof item.id==='string'):[];
      if(!catalog.length)throw new Error('No models');const account=await window.puter.auth.getUser().catch(()=>null);setModels(catalog);setPuterUser(account);setConnected(true);setNotice(`Atlas is connected${account?.username?` as ${account.username}`:''}. AI requests use this Puter account’s allowance or credits.`);
    }catch{setConnected(false);setError('Puter connection was not completed or its model catalogue is unavailable. Your saved work is unchanged.');}finally{setConnecting(false);}
  }
  const persist=message=>portalApi('chat-message',{...scope,message:{id:message.id,role:message.role,content:message.content,status:message.status,model:message.model||'',parts:message.parts||[],route:message.route||{}}});
  async function saveArtifact({file,body,title,summary,sourceMessageId}){
    const op=user.role==='operator'?'artifact-create':'save-draft';
    return portalApi(op,{clientId:document.clientId,projectId:project?.id,title,summary,body:body||'',file,sourceMessageId},{},undefined,{'Idempotency-Key':`atlas-artifact-${sourceMessageId}`});
  }
  const cardFromDetail=item=>({id:item.id,clientId:item.clientId,projectId:item.projectId,kind:item.kind,title:item.title,mimeType:item.version.mime||'text/markdown',fileName:item.version.fileName,status:item.generationStatus||'ready',version:item.version.number,createdAt:item.version.createdAt,excerpt:item.version.body?.slice(0,320)||'',previewUrl:`/api/portal?op=download&id=${encodeURIComponent(item.id)}&version=${encodeURIComponent(item.version.id)}&inline=1`,downloadUrl:`/api/portal?op=download&id=${encodeURIComponent(item.id)}&version=${encodeURIComponent(item.version.id)}`,openUrl:item.url,canRevise:user.role==='operator'});
  async function uploadedAttachment(file,messageId){
    const payload=await portalFilePayload(file);return saveArtifact({file:payload,title:file.name.replace(/\.[^.]+$/,''),summary:'File attached in the Atlas project conversation.',sourceMessageId:`${messageId}-attachment`});
  }
  async function imageDataFromArtifact(artifact){
    const response=await fetch(artifact.previewUrl);if(!response.ok)throw new Error('The reference image could not be opened.');const blob=await response.blob();
    const payload=await portalFilePayload(new File([blob],artifact.fileName||'project-reference.png',{type:blob.type||artifact.mimeType||'image/png'}));return {data:payload.base64,mime:payload.mime};
  }
  async function imageArtifact(image,prompt,messageId,contextPrompt){
    const response=await fetch(image.src);if(!response.ok)throw new Error('The generated image could not be saved.');const blob=await response.blob();
    if(!['image/png','image/jpeg','image/webp'].includes(blob.type))throw new Error('Puter returned an unsupported image format.');
    const ext=blob.type==='image/jpeg'?'jpg':blob.type.split('/')[1];const file=new File([blob],`atlas-${messageId}.${ext}`,{type:blob.type});
    const body=`# Visual brief\n\n${prompt}\n\n## Project context used\n\n${contextPrompt.slice(0,6000)}`;
    return saveArtifact({file:await portalFilePayload(file),body,title:prompt.slice(0,120)||'Atlas creative',summary:project?`Image created for ${project.name} from its saved project context.`:'Image created with Puter and saved to this workspace.',sourceMessageId:messageId});
  }
  function refineImage(artifact){setAction('image');setImageReference(artifact);setAttachment(null);setQuestion('Refine this image: ');setNotice('The existing image is attached as a visual reference. Describe only what you want to change.');requestAnimationFrame(()=>composer.current?.focus());}
  async function ask(rawPrompt,retryId){
    const prompt=rawPrompt.trim();if(!connected||loading||running.current||saving||!prompt)return;
    let route=window.EVPortalChat.routeTask({prompt,action,mode,models,sdk:window.puter.ai});setRouteInfo(route);
    if(route.tool==='unsupported'){setError(route.reason);return;}
    let atlasRun=null;const runKey=retryId||crypto.randomUUID();
    if(project && window.EV_ENABLE_EXPERIMENTAL_RUNTIME === true){
      const skillId=window.EVPortalChat.skillFor({prompt,action,hasImageReference:Boolean(imageReference||attachment?.type?.startsWith('image/'))});
      try{const started=await portalApi('atlas-run-start',{projectId:project.id,conversationId:document.id,skillId,request:prompt},{},undefined,{'Idempotency-Key':`atlas-run-${runKey}`});atlasRun=started.run;route={...route,runId:atlasRun.id,skillId,skillName:atlasRun.skillName};setRouteInfo(route);}
      catch(e){setNotice('Atlas work tracking is temporarily unavailable. Continuing with your Puter request.');}
    }
    running.current=true;const run=++generation.current,index=retryId?messages.findIndex(message=>message.id===retryId):-1;
    const prior=retryId?messages.slice(0,index-1):messages,existingUser=retryId?messages[index-1]:null;
    const sent=existingUser||{id:crypto.randomUUID(),role:'user',content:prompt,status:'complete',model:'',parts:[{type:'text',content:prompt}],route};
    const reply={id:retryId||crypto.randomUUID(),role:'assistant',content:'',status:'streaming',model:route.model||'Puter image generation',parts:[{type:'progress',status:'preparing',label:route.tool==='image'?'Preparing image generation':'Preparing Atlas'}],route};
    const base=[...prior,sent];setMessages([...base,reply]);setQuestion('');setBusy(true);setError('');setNotice('');nearBottom.current=true;
    const controller=new AbortController();abort.current=controller;const timer=setTimeout(()=>controller.abort(new DOMException('Response timed out','TimeoutError')),90000);
    let completed=reply,userSaved=false,runArtifactIds=[];
    const runCapability=route.tool==='image'?(imageReference||attachment?.type?.startsWith('image/')?'puter.image_refine':'puter.image_generate'):'puter.chat';
    try{
      let attachedDetail=null,inputImage='',inputImageMime='';
      if(attachment&&!existingUser){const attachmentPayload=await portalFilePayload(attachment);inputImage=attachment.type.startsWith('image/')?attachmentPayload.base64:'';inputImageMime=attachment.type;attachedDetail=await uploadedAttachment(attachment,sent.id);sent.parts.push({type:'artifact',artifactId:attachedDetail.id,artifact:cardFromDetail(attachedDetail)});}
      else if(imageReference&&!existingUser){const reference=await imageDataFromArtifact(imageReference);inputImage=reference.data;inputImageMime=reference.mime;sent.parts.push({type:'artifact',artifactId:imageReference.id,artifact:imageReference});}
      await persist(sent);userSaved=true;if(run!==generation.current)return;
      if(route.tool==='image'){
        setMessages([...base,{...reply,parts:[{type:'progress',status:'creating',label:'Creating your image'}]}]);
        const contextPrompt=window.EVPortalChat.imagePromptFor(prompt,{project,document,includeContext:allowContext,referenceTitle:imageReference?.title||''});
        const image=await window.puter.ai.txt2img(contextPrompt,inputImage?{model:'gemini-3.1-flash-image',input_image:inputImage,input_image_mime_type:inputImageMime}:{});
        const artifact=await imageArtifact(image,prompt,reply.id,contextPrompt),card=cardFromDetail(artifact);runArtifactIds=[artifact.id];completed={...reply,status:'complete',content:'Your image is saved to this project. Preview it here, or choose “Refine in chat” and describe the next change.',parts:[{type:'text',content:'Your image is saved to this project. Preview it here, or choose **Refine in chat** and describe the next change.'},{type:'artifact',artifactId:artifact.id,artifact:card}]};onSaved?.(artifact,false);setNotice('Image saved. You can keep editing it without leaving this conversation.');
      }else{
        const result=await window.puter.ai.chat(window.EVPortalChat.messagesFor(document,prior,allowContext,prompt,route.task==='document'?'document':'chat',project),{model:route.model,normalize:true,stream:true,max_tokens:4000,signal:controller.signal});
        let output='';output=await window.EVPortalChat.consume(result,{signal:controller.signal,onText:content=>{output=content;if(run===generation.current)setMessages([...base,{...reply,content,parts:[{type:'text',content}]}]);}});
        completed={...reply,status:'complete',content:output,parts:[{type:'text',content:output}]};
        if(route.task==='document'){
          const draft=window.EVPortalChat.draftFor(output),artifact=await saveArtifact({...draft,summary:`Atlas-created project document. Provider model: ${route.model}. Review before use.`,sourceMessageId:reply.id});runArtifactIds=[artifact.id];
          completed.parts.push({type:'artifact',artifactId:artifact.id,artifact:cardFromDetail(artifact)});setNotice('The document is saved to this project with version history.');onSaved?.(artifact,false);
        }
      }
    }catch(e){
      const stopped=e.name==='AbortError'||e.name==='TimeoutError';completed={...reply,status:stopped?'stopped':'error',content:'',parts:[{type:'progress',status:'failed',label:stopped?'Creation stopped':'Creation failed'}]};
      if(run===generation.current)setError(/fund|credit|allowance|402|quota/i.test(`${e.message} ${e.code}`)?'Your Puter allowance or credits are unavailable. No fake artifact was created. Your saved work is still here.':e.name==='TimeoutError'?'The request timed out. You can retry safely.':`Atlas could not complete this request. ${e.message||'Try again.'}`);
    }finally{
      clearTimeout(timer);if(run===generation.current){
        if(atlasRun){try{const runStatus=completed.status==='complete'?'completed':completed.status==='stopped'?'cancelled':'failed';await portalApi('atlas-run-report',{runId:atlasRun.id,status:runStatus,capabilityId:runCapability,artifactIds:runArtifactIds,summary:runStatus==='completed'?`${atlasRun.skillName} completed and the conversation was saved.`:(completed.parts?.[0]?.label||'The provider operation did not complete.'),idempotencyKey:`provider-${reply.id}`},{},undefined,{'Idempotency-Key':`atlas-report-${reply.id}`});}catch(e){setError(`The result is still here, but its Work Summary could not be verified: ${e.message}`);}}
        setMessages(userSaved?[...base,completed]:prior);if(userSaved)try{await persist(completed);}catch(e){setError(`The result could not be saved to this conversation: ${e.message}`);}else setQuestion(prompt);setAttachment(null);setImageReference(null);setAction('');setBusy(false);running.current=false;
      }
    }
  }
  async function saveDraft(message){
    setSaving(true);setError('');try{const draft=window.EVPortalChat.draftFor(message.content),artifact=await saveArtifact({...draft,summary:'Saved from Atlas chat. Review before use.',sourceMessageId:`${message.id}-manual`});const updated={...message,parts:[...(message.parts||[]),{type:'artifact',artifactId:artifact.id,artifact:cardFromDetail(artifact)}]};await persist(updated);setMessages(current=>current.map(item=>item.id===message.id?updated:item));onSaved?.(artifact,false);setNotice('Saved to this project.');}catch(e){setError(e.message);}finally{setSaving(false);}
  }
  async function copy(message){try{await navigator.clipboard.writeText(message.content);setNotice('Response copied.');}catch{setError('Copy is unavailable. Select the response text to copy it.');}}
  const last=messages[messages.length-1],shortcuts=[['copy','Write copy','Write three ad headlines and captions for this project.'],['document','Write a plan','Create a campaign plan for this project.'],['image','Create an image','Create an image for this project.'],['review','Review project','Review this project and recommend the next three actions.']];
  return <section className="portal-assistant portal-chat" aria-label="Atlas project conversation">
    <header className="portal-chat-header"><div><span className="portal-chat-brand"><I name="spark" size={16}/> Atlas</span><span className="portal-chat-private">Private to you · {project?project.name:'this saved work'}</span></div><div className="portal-chat-controls">{connected&&<div className="portal-puter-account"><span><I name="user" size={13}/> Puter{puterUser?.username?` · ${puterUser.username}`:''}</span><button type="button" disabled={busy||saving||connecting} onClick={()=>connect(true)}>{connecting?'Switching…':'Switch account'}</button></div>}<label className="portal-chat-model"><span className="sr-only">Atlas mode</span><select aria-label="Atlas mode" value={mode} disabled={busy} onChange={e=>setMode(e.target.value)}>{Object.entries(window.EVPortalChat.modes).map(([value,item])=><option value={value} key={value}>{item.label}</option>)}</select><small>{window.EVPortalChat.modes[mode].description}</small></label></div></header>
    <div className="portal-chat-shortcuts" aria-label="Creation shortcuts">{shortcuts.map(([value,label,prompt])=><button key={value} disabled={busy} aria-pressed={action===value} onClick={()=>{setAction(value);setQuestion(prompt);composer.current?.focus();}}><I name={value==='image'?'image':value==='document'?'fileText':value==='review'?'search':'pencil'} size={14}/>{label}</button>)}</div>
    <div ref={transcript} className="portal-chat-transcript" role="log" aria-live="polite" aria-relevant="additions" onScroll={e=>{const node=e.currentTarget;nearBottom.current=node.scrollHeight-node.scrollTop-node.clientHeight<90;}}>
      {loading?<p className="portal-muted" role="status">Opening your project conversation…</p>:messages.length===0?<div className="portal-chat-welcome"><span className="portal-chat-emblem"><I name="spark" size={23}/></span><h3>What are we making?</h3><p>Atlas uses this project’s brief, keeps every result here and helps you make the next version.</p><div className="portal-onboarding-steps"><span><b>1</b>Describe the outcome</span><span><b>2</b>Review or refine it</span><span><b>3</b>Keep every version</span></div>{!connected&&<div className="portal-chat-connect-actions"><button className="portal-secondary" disabled={!ready||connecting} onClick={()=>connect(false)}>{connecting?'Connecting…':ready?'Connect Puter':'Preparing connection…'}<I name="ext" size={13}/></button><button className="portal-text-button" disabled={!ready||connecting} onClick={()=>connect(true)}>Use another Puter account</button></div>}<small>Your Puter account powers AI usage. Eleven Views does not silently switch to an agency-funded model.</small></div>:messages.map((message,index)=><article key={message.id} className={`portal-chat-message chat-${message.role}`}><div className="portal-chat-message-label">{message.role==='user'?'You':'Atlas'}{message.role==='assistant'&&message.model&&<span>{message.model}</span>}</div>{(message.parts?.length?message.parts:[{type:'text',content:message.content}]).map((part,n)=>part.type==='text'&&part.content?<PortalText key={n} body={part.content}/>:part.type==='artifact'?<PortalArtifactCard key={n} artifact={part.artifact} onOpen={artifact=>artifact.kind==='image'?setPreviewArtifact(artifact):onSaved?.({id:artifact.id,clientId:artifact.clientId},true)} onRefine={refineImage}/>:part.type==='progress'?<PortalProgress key={n} part={part}/>:null)}{message.role==='assistant'&&message.route?.runId&&message.status==='complete'&&<div className="portal-work-summary"><I name="checkCircle" size={15}/><span><strong>Work Summary</strong>{message.route.skillName||'Atlas work'} · saved to this project</span></div>}{message.role==='assistant'&&message.status!=='streaming'&&<div className="portal-chat-message-actions">{message.content&&(message.parts||[]).every(part=>part.type!=='artifact'||part.artifact?.kind!=='image')&&<><button onClick={()=>copy(message)}>Copy</button><button disabled={busy||saving} onClick={()=>saveDraft(message)}>Save as document</button></>}{message===last&&index>0&&message.status!=='complete'&&<button disabled={busy||saving||!connected} onClick={()=>ask(messages[index-1].content,message.id)}><I name="refresh" size={13}/> Retry</button>}</div>}</article>)}
    </div>
    {!connected&&messages.length>0&&<div className="portal-chat-connect-actions portal-chat-reconnect"><button className="portal-secondary" disabled={!ready||connecting} onClick={()=>connect(false)}>{connecting?'Connecting…':'Connect Puter to continue'}</button><button className="portal-text-button" disabled={!ready||connecting} onClick={()=>connect(true)}>Use another Puter account</button></div>}
    {error&&<div className="portal-alert" role="alert">{error}<button className="portal-text-button" onClick={()=>{setRetry(value=>value+1);if(!ready)setSdkRetry(value=>value+1);}}>Reload chat</button></div>}{notice&&<p className="portal-chat-notice" role="status">{notice}</p>}
    <div className="portal-chat-composer-wrap">{!project&&<div className="portal-chat-project"><label><span>Keep this conversation in a project</span><select defaultValue="" onChange={e=>e.target.value&&onAttachProject?.(e.target.value,scope)}><option value="">Choose project…</option>{projects.map(item=><option key={item.id} value={item.id}>{item.name}</option>)}</select></label><button className="portal-text-button" onClick={()=>onCreateProject?.(scope)}><I name="plus" size={13}/> New project from chat</button></div>}<label className="portal-chat-context"><input type="checkbox" checked={allowContext} disabled={busy||!document.version.body} onChange={e=>setAllowContext(e.target.checked)}/><span>Use {project?'project brief':`“${document.title}” · v${document.version.number}`} as context</span></label>
      {attachment&&<div className="portal-chat-attachment"><I name="paperclip" size={14}/><span>{attachment.name}</span><button onClick={()=>setAttachment(null)} aria-label="Remove attachment"><I name="close" size={13}/></button></div>}{imageReference&&<div className="portal-chat-attachment portal-image-reference"><I name="image" size={14}/><span>Editing from: {imageReference.title}</span><button onClick={()=>setImageReference(null)} aria-label="Remove image reference"><I name="close" size={13}/></button></div>}
      <form className="portal-chat-composer" onSubmit={e=>{e.preventDefault();ask(question);}}><label className="sr-only" htmlFor={`portal-question-${project?.id||document.id}`}>Message Atlas</label><textarea ref={composer} id={`portal-question-${project?.id||document.id}`} value={question} maxLength={4000} rows={2} disabled={!connected||loading||saving} placeholder={connected?'Message Atlas…':'Connect Puter to message Atlas…'} onChange={e=>setQuestion(e.target.value)} onKeyDown={e=>{if(e.key==='Enter'&&!e.shiftKey&&!e.nativeEvent.isComposing){e.preventDefault();e.currentTarget.form.requestSubmit();}}}/><div className="portal-chat-composer-bottom"><div><button type="button" className="portal-chat-attach" onClick={()=>fileInput.current?.click()} aria-label="Attach PDF or image"><I name="paperclip" size={16}/></button><input ref={fileInput} className="sr-only" type="file" accept="application/pdf,image/png,image/jpeg,image/webp" onChange={e=>setAttachment(e.target.files[0]||null)}/><button type="button" className="portal-route-details" onClick={()=>setDetails(value=>!value)}>{routeInfo?`${routeInfo.task} · ${window.EVPortalChat.modes[mode].label}`:window.EVPortalChat.modes[mode].label}</button></div>{busy?<button type="button" className="portal-chat-send portal-chat-stop" aria-label="Stop response" onClick={()=>abort.current?.abort()}><span/></button>:<button type="submit" className="portal-chat-send" aria-label="Send message" disabled={!connected||loading||saving||!question.trim()}><I name="arrowUp" size={17}/></button>}</div></form>
      {details&&<div className="portal-route-panel"><strong>{routeInfo?.reason||window.EVPortalChat.modes[mode].description}</strong><span>{routeInfo?.model?`Provider: ${routeInfo.provider||'Puter'} · Model: ${routeInfo.model}`:'The exact provider/model appears here after Atlas routes a request.'}</span></div>}
      <p className="portal-chat-disclaimer">AI can make mistakes. Review before using. Requests draw from your Puter allowance or credits. Generated work is saved only after a real file or document exists. Publishing and ad spend always require a separate authorized workflow.</p>
    </div>{previewArtifact&&<PortalImagePreview artifact={previewArtifact} onClose={()=>setPreviewArtifact(null)} onRefine={refineImage}/>}
  </section>;
}
function PortalManage({ clientId, clients, refresh }) {
  const [name, setName] = React.useState(''), [inviteName, setInviteName] = React.useState(''), [inviteEmail, setInviteEmail] = React.useState('');
  const [link, setLink] = React.useState(''), [error, setError] = React.useState(''), [busy, setBusy] = React.useState(false);
  const [members, setMembers] = React.useState([]);
  const [linkDays, setLinkDays] = React.useState(7);
  async function loadMembers() { if (clientId) { const data = await portalApi('members', undefined, { clientId }); setMembers(data.members); } else setMembers([]); }
  async function create(e) { e.preventDefault(); setBusy(true); setError(''); try { await portalApi('client-create', { name }); setName(''); await refresh(); } catch (e) { setError(e.message); } finally { setBusy(false); } }
  async function invite(e) { e.preventDefault(); setBusy(true); setError(''); setLink(''); try { const data = await portalApi('invite', { clientId, name: inviteName, email: inviteEmail }); setLinkDays(data.expiresInDays); setLink(data.url); } catch (e) { setError(e.message); } finally { setBusy(false); } }
  React.useEffect(() => { setLink(''); setError(''); setMembers([]); loadMembers().catch(e => setError(e.message)); }, [clientId]);
  async function revoke(userId) { setBusy(true); setError(''); try { await portalApi('revoke-user', { userId }); await loadMembers(); } catch (e) { setError(e.message); } finally { setBusy(false); } }
  async function resetAccess(member) { setBusy(true); setError(''); setLink(''); try { const data = await portalApi('reset-access', { userId: member.id }); setInviteName(member.name); setInviteEmail(member.email); setLinkDays(data.expiresInDays); setLink(data.url); } catch (e) { setError(e.message); } finally { setBusy(false); } }
  return <div className="portal-management"><div className="portal-kicker">STUDIO CONTROLS</div><h1>Make room for a client.</h1><p>Each workspace has its own inbox, documents and invited team.</p>
    <div className="portal-management-grid"><form onSubmit={create}><h2>Create a workspace</h2><label>Organization name<input required maxLength={120} value={name} onChange={e => setName(e.target.value)} placeholder="Your client’s organization" /></label><button className="portal-primary" disabled={busy}>Create workspace</button></form>
      <form onSubmit={invite}><h2>Invite a client</h2><p>Selected workspace: <strong>{clients.find(c => c.id === clientId)?.name || 'Choose a workspace first'}</strong></p><label>Name<input required maxLength={100} value={inviteName} onChange={e => setInviteName(e.target.value)} /></label><label>Email<input required type="email" value={inviteEmail} onChange={e => setInviteEmail(e.target.value)} /></label><button className="portal-secondary" disabled={busy || !clientId}>Create invitation link</button>
        {link && <div className="portal-invitation"><label>Private access link, valid for {linkDays} {linkDays === 1 ? 'day' : 'days'}<textarea rows={4} readOnly value={link} onFocus={e => e.target.select()} /></label><p>Share this link directly with the named person. No email has been sent.</p><button type="button" className="portal-secondary" onClick={() => navigator.clipboard.writeText(link).catch(() => setError('Select and copy the invitation link.'))}>Copy access link</button></div>}
      </form></div>{error && <p className="portal-alert" role="alert">{error}</p>}
    {members.length > 0 && <div className="portal-members"><h2>Workspace access</h2>{members.map(m => <div key={m.id}><span><strong>{m.name}</strong><small>{m.email}</small></span><span className="portal-member-actions"><button className="portal-secondary" disabled={busy || !m.active} onClick={() => resetAccess(m)}>Reset password link</button><button className="portal-secondary" disabled={busy || !m.active} onClick={() => revoke(m.id)}>{m.active ? 'Revoke access' : 'Access revoked'}</button></span></div>)}</div>}
    <div className="portal-agent-note"><I name="monitor" /><div><h3>Connected agents deliver here.</h3><p>Give each backend agent a key scoped to the correct workspace. Agent submissions arrive as studio drafts. Review and release them from the inbox.</p><p className="portal-mono">POST /api/portal?op=artifact-create</p></div></div>
  </div>;
}
function PortalLibrary({items,query,setQuery,onOpen,onNew,onRefresh,canCreate}) {
  const [kind,setKind]=React.useState('all'),[view,setView]=React.useState('grid');
  const visible=items.filter(item=>kind==='all'||item.kind===kind);
  return <section className="portal-library" aria-label="Deliverables library">
    <header className="portal-library-header"><div><span className="portal-kicker">YOUR SAVED WORK</span><h1>Library<span>{items.length}</span></h1><p>Plans, creative and deliverables. Ready when you are.</p></div><button className="portal-primary" onClick={onNew} disabled={!canCreate}><I name="plus" size={15}/> New document</button></header>
    <div className="portal-library-tools"><div className="portal-library-filters" role="group" aria-label="File type">{[['all','All files'],['document','Documents'],['image','Images'],['pdf','PDFs']].map(([value,label])=><button key={value} aria-pressed={kind===value} onClick={()=>setKind(value)}>{label}</button>)}</div><label className="portal-library-search"><I name="search" size={16}/><span className="sr-only">Search library</span><input value={query} onChange={e=>setQuery(e.target.value)} placeholder="Search library"/></label><div className="portal-library-views" role="group" aria-label="Library layout"><button aria-label="Grid view" aria-pressed={view==='grid'} onClick={()=>setView('grid')}>▦</button><button aria-label="List view" aria-pressed={view==='list'} onClick={()=>setView('list')}>☷</button><button aria-label="Refresh library" onClick={onRefresh}><I name="clock" size={15}/></button></div></div>
    <div className={`portal-library-files library-${view}`}>{visible.map(item=><button className="portal-file-card" key={item.id} onClick={()=>onOpen(item)} aria-label={`Open ${item.title}`}><div className="portal-file-top"><I name={item.kind==='image'?'monitor':'mail'} size={16}/><span>{item.kind==='document'?'DOCUMENT':item.kind.toUpperCase()}</span><span>↗</span></div><div className={`portal-file-preview preview-${item.kind}`} aria-hidden="true">{item.kind==='image'&&item.hasFile?<img loading="lazy" src={`/api/portal?op=download&id=${encodeURIComponent(item.id)}&version=${encodeURIComponent(item.versionId)}&inline=1`} alt=""/>:item.kind==='document'&&item.preview?<div className="portal-file-page"><span>ELEVEN VIEWS / {item.version.toString().padStart(2,'0')}</span><PortalText body={item.preview}/></div>:<div className="portal-file-symbol"><I name="monitor" size={42}/><span>{item.kind.toUpperCase()}</span><small>{item.summary}</small></div>}</div><div className="portal-file-info"><h2>{item.title}</h2><p>{item.summary}</p><div><span className={`portal-status status-${item.status}`}>{portalLabels[item.status]}</span><span>V{item.version} · {portalDate(item.updatedAt)}</span></div></div></button>)}</div>
    {!visible.length&&<div className="portal-library-empty"><I name="spark" size={32}/><h2>A place for what’s next.</h2><p>{query?'No files match this search.':kind==='all'?'Deliverables shared with you will appear here.':'No files of this type yet. Try All files.'}</p></div>}
  </section>;
}
function PortalProjectModal({clientId,user,source,onClose,onCreated}){
  const [name,setName]=React.useState(''),[brief,setBrief]=React.useState(''),[goal,setGoal]=React.useState('grow'),[busy,setBusy]=React.useState(false),[error,setError]=React.useState('');
  const nameRef=React.useRef(null);React.useEffect(()=>{nameRef.current?.focus();const close=e=>e.key==='Escape'&&onClose();window.document.addEventListener('keydown',close);return()=>window.document.removeEventListener('keydown',close);},[]);
  async function submit(e){e.preventDefault();setBusy(true);setError('');try{const data=await portalApi('project-create',{clientId,name,brief,goal,...(source?{sourceArtifactId:source.artifactId,sourceVersionId:source.versionId}:{})},{},undefined,{'Idempotency-Key':`project-${crypto.randomUUID()}`});onCreated(data);onClose();}catch(e){setError(e.message);}finally{setBusy(false);}}
  return <div className="portal-modal-backdrop"><section className="portal-modal portal-project-modal" role="dialog" aria-modal="true" aria-labelledby="project-modal-title"><div className="portal-modal-heading"><div><span className="portal-kicker">NEW PROJECT</span><h2 id="project-modal-title">Give the work a home.</h2></div><button className="portal-icon" onClick={onClose} aria-label="Close"><I name="close"/></button></div><form onSubmit={submit}><label>Project name<input ref={nameRef} required maxLength={120} value={name} onChange={e=>setName(e.target.value)} placeholder="October campaign"/></label><label>What are we working toward?<textarea rows={5} maxLength={20000} value={brief} onChange={e=>setBrief(e.target.value)} placeholder="The audience, offer, outcome and anything Atlas should know."/></label><label>Primary goal<select value={goal} onChange={e=>setGoal(e.target.value)}><option value="launch">Launch something</option><option value="grow">Grow the business</option><option value="manage">Manage ongoing work</option></select></label>{source&&<p className="portal-project-source"><I name="msg" size={15}/> The current conversation will be added to this project.</p>}{error&&<p className="portal-alert" role="alert">{error}</p>}<div className="portal-modal-actions"><small>A private project brief and Atlas conversation will be created in this workspace.</small><button className="portal-primary" disabled={busy}>{busy?'Creating…':'Create project'}<I name="arrow" size={14}/></button></div></form></section></div>;
}
function PortalProjects({projects,onOpen,onNew}){
  return <section className="portal-projects"><header className="portal-projects-header"><div><span className="portal-kicker">PROJECTS</span><h1>Everything for the work,<br/><em>kept together.</em></h1><p>Conversations, drafts and finished deliverables stay with the project that created them.</p></div><button className="portal-primary" onClick={onNew}><I name="plus" size={15}/> New project</button></header>{projects.length?<div className="portal-project-grid">{projects.map(project=><button key={project.id} onClick={()=>onOpen(project)}><span className="portal-project-icon"><I name="folder" size={21}/></span><span className="portal-project-state">{project.status}</span><h2>{project.name}</h2><p>{project.brief||'Open the project and start working with Atlas.'}</p><footer><span>{project.artifactCount} files</span><span>{project.messageCount} messages</span><I name="arrow" size={15}/></footer></button>)}</div>:<div className="portal-project-empty"><span className="portal-project-icon"><I name="folder" size={26}/></span><h2>Your first project starts here.</h2><p>Create one place for the brief, the conversation and every version Atlas helps you make.</p><button className="portal-secondary" onClick={onNew}>Create a project</button></div>}</section>;
}
function PortalProjectWorkspace({data,user,projects,onBack,onOpenArtifact,onSaved,onAttachProject,starterPrompt}){
  const {project,artifacts,contextArtifact}=data;
  return <section className="portal-project-workspace"><header className="portal-project-workspace-head"><button className="portal-icon" onClick={onBack} aria-label="Back to projects"><I name="arrowL" size={17}/></button><div><span className="portal-kicker">{project.goal} PROJECT</span><h1>{project.name}</h1><p>{project.brief||'Use Atlas to shape the next step and create the work.'}</p></div></header><div className="portal-project-layout"><div className="portal-project-chat">{contextArtifact?<PortalAssistant document={contextArtifact} user={user} project={project} projects={projects} onSaved={onSaved} onAttachProject={onAttachProject} starterPrompt={starterPrompt}/>:<div className="portal-alert">This project is missing its private context document. Reopen it or contact Eleven Views.</div>}</div><aside className="portal-project-files"><header><div><span className="portal-kicker">PROJECT FILES</span><h2>{artifacts.length}</h2></div></header>{artifacts.length?<div>{artifacts.map(artifact=><PortalArtifactCard key={artifact.id} artifact={artifact} onOpen={onOpenArtifact}/>)}</div>:<p className="portal-muted">Files created or attached in this conversation will collect here.</p>}</aside></div></section>;
}
function PortalDashboard({user,workspace,items,projects,onOpen,onNewProject,onAtlas,onNavigate}) {
  const [prompt,setPrompt]=React.useState(''),[controlOpen,setControlOpen]=React.useState(false);
  const recent=items.slice(0,4),reviewCount=items.filter(item=>item.status==='review'||item.status==='changes_requested').length;
  function start(e){e.preventDefault();if(prompt.trim())onAtlas(prompt.trim());}
  function navigate(destination){setControlOpen(false);onNavigate(destination);}
  React.useEffect(()=>{if(!controlOpen)return;const close=e=>{if(e.key==='Escape')setControlOpen(false);};window.document.addEventListener('keydown',close);return()=>window.document.removeEventListener('keydown',close);},[controlOpen]);
  return <section className="portal-dashboard" aria-label="Workspace home">
    <header className="portal-dashboard-header"><div><span className="portal-kicker">{workspace?.name||'YOUR ELEVEN VIEWS WORKSPACE'}</span><h1>Welcome back, {user.name.split(' ')[0]}.</h1><p>Start with Atlas or pick up the work already in motion.</p></div><div className="portal-dashboard-actions"><button className="portal-primary" onClick={onNewProject}><I name="plus" size={15}/> New project</button><button className="portal-control-button" aria-label="Open control center" aria-expanded={controlOpen} aria-controls="portal-control-center" title="Control center" onClick={()=>setControlOpen(value=>!value)}><span aria-hidden="true">C</span></button>{controlOpen&&<><button className="portal-control-scrim" aria-label="Close control center" onClick={()=>setControlOpen(false)}/><aside className="portal-control-panel" id="portal-control-center" aria-label="Control center"><header><div><span className="portal-kicker">CONTROL CENTER</span><h2>Workspace status</h2></div><button className="portal-icon" onClick={()=>setControlOpen(false)} aria-label="Close control center"><I name="close" size={16}/></button></header><button onClick={()=>navigate('projects')}><span><I name="folder" size={17}/> Active projects</span><strong>{projects.length.toString().padStart(2,'0')}</strong></button><button onClick={()=>navigate('approvals')}><span><I name="checkCircle" size={17}/> For review</span><strong>{reviewCount.toString().padStart(2,'0')}</strong></button><button onClick={()=>navigate('library')}><span><I name="grid" size={17}/> Saved files</span><strong>{items.length.toString().padStart(2,'0')}</strong></button><button onClick={()=>navigate('billing')}><span><I name="user" size={17}/> Requests &amp; invoices</span><strong>↗</strong></button><small>Open a section to move directly into the work.</small></aside></>}</div></header>
    <div className="portal-dashboard-work"><div className="portal-dashboard-atlas"><span className="portal-chat-emblem"><I name="spark" size={24}/></span><h2>What are we working on?</h2><form onSubmit={start}><label className="sr-only" htmlFor="dashboard-atlas-prompt">Start with Atlas</label><textarea id="dashboard-atlas-prompt" rows={2} maxLength={4000} value={prompt} onChange={e=>setPrompt(e.target.value)} placeholder="A campaign, a new offer, a question…"/><button className="portal-chat-send" disabled={!prompt.trim()||!projects.length} aria-label="Continue with Atlas"><I name="arrowUp" size={17}/></button></form><small>{projects.length?'Your message opens in your most recent project. Nothing is sent until you connect Puter and press send.':'Create a project first so Atlas can keep the conversation and every deliverable together.'}</small></div>
    <section className="portal-dashboard-recent"><header><div><span className="portal-kicker">RECENT WORK</span><h2>Pick up where you left off.</h2></div>{items.length>4&&<button className="portal-text-button" onClick={()=>onOpen(null)}>View all</button>}</header>{recent.length?<div>{recent.map(item=><button key={item.id} onClick={()=>onOpen(item)}><span className={`portal-dashboard-file type-${item.kind}`}><I name={artifactIcon(item.kind)} size={20}/></span><span><strong>{item.title}</strong><small>{item.kind.toUpperCase()} · V{item.version} · {portalDate(item.updatedAt)}</small></span><I name="arrow" size={15}/></button>)}</div>:<div className="portal-dashboard-empty"><p>Your project files will collect here.</p><button className="portal-secondary" onClick={onNewProject}>Create your first project</button></div>}</section></div>
  </section>;
}
function PortalBilling({operator=false}){ return <CommerceHistory operator={operator}/>; }
function ClientPortal() {
  const [user, setUser] = React.useState(null), [checking, setChecking] = React.useState(true), [loginError, setLoginError] = React.useState('');
  const [clients, setClients] = React.useState([]), [clientId, setClientId] = React.useState('');
  const [items, setItems] = React.useState([]), [selected, setSelected] = React.useState(''), [document, setDocument] = React.useState(null);
  const [projects,setProjects]=React.useState([]),[selectedProject,setSelectedProject]=React.useState(''),[projectData,setProjectData]=React.useState(null),[projectModal,setProjectModal]=React.useState(null);
  const [version, setVersion] = React.useState(''), [tab, setTab] = React.useState(()=>new URLSearchParams(window.location.search).get('tab')==='billing'?'billing':'home'), [sideTab, setSideTab] = React.useState('feedback');
  const [query, setQuery] = React.useState(''), [loading, setLoading] = React.useState(false), [error, setError] = React.useState('');
  const [feedback, setFeedback] = React.useState(''), [busy, setBusy] = React.useState(false), [compose, setCompose] = React.useState(null), [tick, setTick] = React.useState(0), [atlasStarter,setAtlasStarter]=React.useState(''),[billing,setBilling]=React.useState(null);
  const initialLink = React.useRef(Object.fromEntries(new URLSearchParams(window.location.search))), linkOpened = React.useRef(false);
  const operator = user?.role === 'operator';
  React.useEffect(() => {
    const controller = new AbortController();
    portalApi('session', undefined, {}, controller.signal).then(data => setUser(data.user)).catch(e => { if (e.status !== 401 && e.name !== 'AbortError') setLoginError(e.message); }).finally(() => setChecking(false));
    return () => controller.abort();
  }, []);
  async function loadClients() {
    const data = await portalApi('clients'); setClients(data.clients);
    setClientId(prev => data.clients.some(c => c.id === prev) ? prev : data.clients.find(c => c.id === initialLink.current.client)?.id || data.clients[0]?.id || '');
  }
  React.useEffect(() => { if (user) loadClients().catch(e => setError(e.message)); }, [user]);
  React.useEffect(() => { setSelected(''); setDocument(null); setItems([]); setProjects([]);setSelectedProject('');setProjectData(null);setVersion(''); setFeedback(''); setError(''); }, [clientId]);
  React.useEffect(() => {
    if (!user || !clientId) return;
    const controller = new AbortController();
    function load() { portalApi('inbox', undefined, { clientId }, controller.signal).then(data => {
      setItems(data.items);
      if (!linkOpened.current && initialLink.current.artifact && data.items.some(i => i.id === initialLink.current.artifact)) {
        setSelected(initialLink.current.artifact); setTab('library'); linkOpened.current = true;
      }
    }).catch(e => { if (e.name !== 'AbortError') setError(e.message); }); }
    load(); const interval = setInterval(load, 30000);
    return () => { controller.abort(); clearInterval(interval); };
  }, [user, clientId, tick]);
  React.useEffect(()=>{
    if(!user||!clientId)return;const controller=new AbortController();portalApi('projects',undefined,{clientId},controller.signal).then(data=>{setProjects(data.projects);if(!linkOpened.current&&initialLink.current.project&&data.projects.some(project=>project.id===initialLink.current.project)){setSelectedProject(initialLink.current.project);setTab('projects');linkOpened.current=true;}}).catch(e=>{if(e.name!=='AbortError')setError(e.message);});return()=>controller.abort();
  },[user,clientId,tick]);
  React.useEffect(()=>{if(!user||!clientId||tab!=='billing')return;const controller=new AbortController();setBilling(null);portalApi('plan',undefined,{clientId},controller.signal).then(setBilling).catch(e=>{if(e.name!=='AbortError')setError(e.message);});return()=>controller.abort();},[user,clientId,tab,tick]);
  React.useEffect(()=>{
    if(!selectedProject){setProjectData(null);return;}const controller=new AbortController();setLoading(true);portalApi('project',undefined,{id:selectedProject},controller.signal).then(setProjectData).catch(e=>{if(e.name!=='AbortError')setError(e.message);}).finally(()=>{if(!controller.signal.aborted)setLoading(false);});return()=>controller.abort();
  },[selectedProject,tick]);
  React.useEffect(() => {
    if (!selected) { setDocument(null); return; }
    const controller = new AbortController(); setLoading(true); setDocument(null); setFeedback('');
    portalApi('artifact', undefined, { id: selected, ...(version ? { version } : {}) }, controller.signal).then(data => {
      setDocument(data); portalApi('read', { id: data.id, versionId: data.version.id }).catch(() => {});
      setItems(prev => prev.map(i => i.id === data.id ? { ...i, unread: false } : i));
    }).catch(e => { if (e.name !== 'AbortError') setError(e.message); }).finally(() => { if (!controller.signal.aborted) setLoading(false); });
    return () => controller.abort();
  }, [selected, version, tick]);
  async function change(op, data) {
    setBusy(true); setError('');
    try { const result = await portalApi(op, data); setDocument(result); setFeedback(''); setTick(t => t + 1); }
    catch (e) { setError(e.message); } finally { setBusy(false); }
  }
  async function logout() {
    try { await portalApi('logout', {}); setUser(null); setClientId(''); setClients([]); setItems([]);setProjects([]);setSelectedProject('');setProjectData(null);setSelected(''); setDocument(null); }
    catch (e) { setError(e.message); }
  }
  function open(item) { setSelected(item.id); setVersion(''); setError(''); window.history.replaceState({}, '', `/portal?client=${encodeURIComponent(clientId)}&artifact=${encodeURIComponent(item.id)}`); }
  function closeDocument(){setSelected('');setVersion('');window.history.replaceState({},'',`/portal?client=${encodeURIComponent(clientId)}`);}
  function saved(item,openItem=true) { setTick(t => t + 1);if(!openItem)return;setSelectedProject('');setSelected(item.id); setVersion(''); setTab('library'); setSideTab('feedback'); window.history.replaceState({}, '', `/portal?client=${encodeURIComponent(item.clientId||clientId)}&artifact=${encodeURIComponent(item.id)}`); }
  function openFromHome(item){if(!item){setTab('library');closeDocument();return;}setTab('library');open(item);}
  function openProject(project){setSelected('');setDocument(null);setSelectedProject(project.id);setTab('projects');setAtlasStarter('');window.history.replaceState({},'',`/portal?client=${encodeURIComponent(clientId)}&project=${encodeURIComponent(project.id)}`);}
  function closeProject(){setSelectedProject('');setProjectData(null);setAtlasStarter('');window.history.replaceState({},'',`/portal?client=${encodeURIComponent(clientId)}`);}
  function startAtlas(prompt){const project=projects[0];if(!project)return;setAtlasStarter(prompt);setSelectedProject(project.id);setTab('projects');window.history.replaceState({},'',`/portal?client=${encodeURIComponent(clientId)}&project=${encodeURIComponent(project.id)}`);}
  async function attachProject(projectId,scope){try{await portalApi('project-attach-chat',{projectId,...scope});setTick(value=>value+1);setSelectedProject(projectId);setTab('projects');}catch(e){setError(e.message);}}
  function projectCreated(data){setTick(value=>value+1);setProjects(current=>[data.project,...current.filter(item=>item.id!==data.project.id)]);setProjectData({project:data.project,contextArtifact:data.contextArtifact,artifacts:[cardFromPortalDetail(data.contextArtifact)]});setSelectedProject(data.project.id);setTab('projects');window.history.replaceState({},'',`/portal?client=${encodeURIComponent(clientId)}&project=${encodeURIComponent(data.project.id)}`);}
  function cardFromPortalDetail(item){return {id:item.id,clientId:item.clientId,projectId:item.projectId,kind:item.kind,title:item.title,mimeType:item.version.mime||'text/markdown',fileName:item.version.fileName,status:item.generationStatus||'ready',version:item.version.number,createdAt:item.version.createdAt,excerpt:item.version.body?.slice(0,320)||'',previewUrl:`/api/portal?op=download&id=${encodeURIComponent(item.id)}&version=${encodeURIComponent(item.version.id)}&inline=1`,downloadUrl:`/api/portal?op=download&id=${encodeURIComponent(item.id)}&version=${encodeURIComponent(item.version.id)}`,openUrl:item.url,canRevise:user.role==='operator'};}
  const filtered = items.filter(i => (tab !== 'approvals' || i.status === 'review' || i.status === 'changes_requested') && `${i.title} ${i.summary}`.toLowerCase().includes(query.toLowerCase()));
  const workspace = clients.find(c => c.id === clientId);
  if (checking) return <main className="portal-loading" role="status">Opening your workspace…</main>;
  if (!user) return <PortalLogin onLogin={setUser} initialError={loginError} />;
  const historic = document && document.version.id !== document.currentVersionId;
  const download = document ? `/api/portal?op=download&id=${encodeURIComponent(document.id)}&version=${encodeURIComponent(document.version.id)}` : '';
  return <main className={`portal-app ${tab==='library'?'library-active':''} ${tab==='home'?'dashboard-active':''}`}><aside className="portal-sidebar"><div className="portal-sidebar-brand"><Logo size={46}/><span>ELEVEN VIEWS</span></div>
    <div className="portal-workspace-name">{clients.length>1 ? <label className="portal-workspace-select"><span className="sr-only">Workspace</span><select value={clientId} onChange={e => setClientId(e.target.value)}><option value="" disabled>Choose a workspace</option>{clients.map(c => <option key={c.id} value={c.id}>{c.name}</option>)}</select><I name="arrowDown" size={14}/></label> : <h2>{workspace?.name || 'Your workspace'}</h2>}<span>YOUR WORKSPACE</span></div>
    <button className="portal-primary portal-new" disabled={!clientId} onClick={() => setProjectModal({})}><I name="plus" size={15} /> New project</button>
    <nav aria-label="Workspace navigation"><button className={tab==='home'?'active':''} onClick={()=>{setTab('home');closeDocument();closeProject();}}><I name="spark" size={17}/><span>Home</span></button><button className={tab==='projects'?'active':''} onClick={()=>{setTab('projects');closeDocument();}}><I name="folder" size={17}/><span>Projects</span><b>{projects.length}</b></button><button className={tab==='library'?'active':''} onClick={()=>{setTab('library');closeDocument();closeProject();}}><I name="grid" size={17}/><span>Library</span><b>{items.length}</b></button><button className={tab === 'inbox' ? 'active' : ''} onClick={() => {setTab('inbox');closeProject();}}><I name="mail" size={17}/><span>Updates</span><b>{items.filter(i => i.unread).length || ''}</b></button><button className={tab === 'approvals' ? 'active' : ''} onClick={() => {setTab('approvals');closeProject();}}><I name="check" size={17}/><span>Review</span><b>{items.filter(i => i.status === 'review' || i.status === 'changes_requested').length || ''}</b></button><button className="portal-mobile-signout" onClick={logout}><I name="ext" size={15}/><span>Sign out</span></button></nav>
    <div className="portal-sidebar-note"><span className="portal-kicker">A SHARED PLACE</span><p>Every plan, every revision and the work you keep.</p><a href="/contact">Talk to the studio <span>↗</span></a></div>
    <div className="portal-user"><div className="portal-avatar">{user.name.slice(0, 1)}</div><div><strong>{user.name}</strong><span>{user.email}</span></div><button onClick={logout} className="portal-icon" aria-label="Sign out" title="Sign out"><I name="ext" size={15} /></button></div>
  </aside><div className="portal-main">{error && <div className="portal-alert portal-global-alert" role="alert">{error}<button onClick={() => setError('')} aria-label="Dismiss message">×</button></div>}
    {tab==='home'?<PortalDashboard user={user} workspace={workspace} items={items} projects={projects} onOpen={openFromHome} onNewProject={()=>setProjectModal({})} onAtlas={startAtlas} onNavigate={destination=>{setTab(destination);closeDocument();closeProject();}}/>:tab==='billing'?<PortalBilling operator={user?.role==='operator'} data={billing} onRefresh={()=>setTick(value=>value+1)}/>:tab==='projects'?(selectedProject?(loading||!projectData?<div className="portal-loading">Opening project…</div>:<PortalProjectWorkspace data={projectData} user={user} projects={projects} onBack={closeProject} onOpenArtifact={artifact=>saved(artifact,true)} onSaved={saved} onAttachProject={attachProject} starterPrompt={atlasStarter}/>):<PortalProjects projects={projects} onOpen={openProject} onNew={()=>setProjectModal({})}/>):tab==='library'&&!selected?<PortalLibrary items={filtered} query={query} setQuery={setQuery} onOpen={open} onNew={()=>setCompose({})} onRefresh={()=>setTick(t=>t+1)} canCreate={Boolean(clientId)}/>:<>
      <header className="portal-heading"><div><span className="portal-kicker">YOUR WORKSPACE</span><h1>{tab === 'approvals' ? 'A fresh set of eyes.' : 'Your updates.'}<span>{filtered.length.toString().padStart(2, '0')}</span></h1><p>Good work starts with a conversation. Here’s what’s new.</p></div><button className="portal-secondary" onClick={() => setTick(t => t + 1)}><I name="clock" size={14} /> Refresh</button></header>
      <div className={`portal-workbench ${selected ? 'has-document' : ''}`}><section className="portal-inbox" aria-label="Shared documents"><label className="portal-search"><I name="search" size={16} /><span className="sr-only">Search documents</span><input value={query} onChange={e => setQuery(e.target.value)} placeholder="Find a document" /></label>
        {!clientId || filtered.length === 0 ? <div className="portal-empty-list"><I name="mail" size={28} /><h3>{!clientId ? 'Start with a workspace.' : 'A little room for what’s next.'}</h3><p>{!clientId ? 'Your workspace will appear here when it is ready.' : query ? 'Try another search.' : 'Shared documents will appear here.'}</p></div> : filtered.map(item => <button key={item.id} className={`portal-inbox-item ${selected === item.id ? 'selected' : ''}`} onClick={() => open(item)}>
          <div className="portal-inbox-item-top"><span className={`portal-status status-${item.status}`}>{portalLabels[item.status]}</span><time>{portalDate(item.updatedAt)}</time></div><h3>{item.unread && <span className="portal-unread" aria-label="Unread" />}{item.title}</h3><p>{item.summary || 'A document from your shared workspace.'}</p><div className="portal-inbox-item-bottom"><span>{item.kind === 'document' ? 'DOCUMENT' : item.kind.toUpperCase()} · V{item.version}</span><span>Open ↗</span></div>
        </button>)}
      </section><section className={`portal-reader ${sideTab === 'assistant' ? 'chat-mode' : ''}`} aria-label="Document preview">
        {!selected ? <div className="portal-reader-empty"><div className="portal-paper-symbol"><I name="monitor" size={35} /></div><span className="portal-kicker">YOUR WORK, WITH CONTEXT</span><h2>Open something<br /><em>worth a closer look.</em></h2><p>Read it here. Leave a thought. Keep a copy.<br />Each version has a place.</p></div> : loading || !document ? <div className="portal-loading" role="status">Opening document…</div> : <>
          <div className="portal-reader-toolbar"><button className="portal-icon" onClick={closeDocument} aria-label={tab==='library'?'Back to library':'Close document'}><I name="arrowL" size={17} /></button>{tab==='library'&&<span className="portal-reader-breadcrumb">Library / {document.kind==='document'?'Document':document.kind.toUpperCase()}</span>}<label><span className="sr-only">Document version</span><select value={document.version.id} onChange={e => setVersion(e.target.value)}>{document.versions.map(v => <option key={v.id} value={v.id}>Version {v.number}{v.id === document.currentVersionId ? ' · current' : ''}</option>)}</select></label><a className="portal-download" href={download}>Download <I name="arrowUp" size={14} /></a></div>
          <div className="portal-artifact-heading"><span className={`portal-status status-${document.status}`}>{portalLabels[document.status]}</span><h2>{document.title}</h2><p>{document.summary}</p>{historic && <p className="portal-history-note">You are viewing an earlier version.</p>}</div>
          <div className="portal-preview">{document.kind!=='document'&&<PortalMedia key={document.version.id} document={document} url={download}/>} {document.version.body.trim()&&<><div className="portal-print-action"><button className="portal-secondary" onClick={()=>window.print()}>Print / Save PDF</button></div><PortalText body={document.version.body}/></>}</div>
          <div className="portal-review-actions">{operator ? <>{document.kind === 'document' && <button className="portal-secondary" disabled={historic} onClick={() => setCompose({ editing: document })}>Make a revision</button>}<small>This work stays saved in your Eleven Views workspace.</small></>
            : document.status !== 'draft' && <><button className="portal-primary" disabled={busy || historic || document.status === 'approved'} onClick={() => change('decision', { id: document.id, versionId: document.version.id, status: 'approved' })}>{document.status === 'approved' ? 'Approved' : 'Approve this version'} <I name="check" size={14} /></button><small>Approval applies to this version. Publishing is arranged separately.</small></>}
          </div>
          <div className="portal-detail-tabs" role="tablist" aria-label="Document tools"><button role="tab" aria-selected={sideTab === 'feedback'} onClick={() => setSideTab('feedback')}>Team feedback <span>{document.comments.length}</span></button><button role="tab" aria-selected={sideTab === 'assistant'} onClick={() => setSideTab('assistant')}>Chat with Atlas <I name="spark" size={13} /></button></div>
          {sideTab === 'assistant' ? <PortalAssistant document={document} user={user} projects={projects} onSaved={saved} onAttachProject={attachProject} onCreateProject={source=>setProjectModal({source})} starterPrompt={atlasStarter} /> : <div className="portal-conversation">
            {document.comments.length === 0 && <p className="portal-muted">A question, a small change, a new idea. Leave it here.</p>}{document.comments.map(c => <article className="portal-comment" key={c.id}><header><strong>{c.author}</strong><time>{portalDate(c.createdAt)}</time></header><p>{c.body}</p></article>)}
            <form onSubmit={e => { e.preventDefault(); change('comment', { id: document.id, versionId: document.version.id, body: feedback }); }}><label className="sr-only" htmlFor="portal-feedback">Your feedback</label><textarea id="portal-feedback" maxLength={5000} rows={3} value={feedback} onChange={e => setFeedback(e.target.value)} placeholder="Leave a note for the team" /><div className="portal-feedback-actions"><button className="portal-secondary" disabled={busy || !feedback.trim()}>Send note <I name="send" size={14} /></button>{!operator && document.status !== 'draft' && <button type="button" className="portal-text-button" disabled={busy || historic || !feedback.trim()} onClick={() => change('decision', { id: document.id, versionId: document.version.id, status: 'changes_requested', comment: feedback })}>Request changes</button>}</div></form>
          </div>}
        </>}
      </section></div>
    </>}
  </div>{compose && <PortalCompose user={user} clientId={clientId} editing={compose.editing} onClose={() => setCompose(null)} onSaved={saved} />}{projectModal&&<PortalProjectModal clientId={clientId} user={user} source={projectModal.source} onClose={()=>setProjectModal(null)} onCreated={projectCreated}/>}</main>;
}
window.ClientPortal = ClientPortal;
