const esc=v=>String(v??'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
const login=document.querySelector('#login-form');
login?.addEventListener('submit',async e=>{e.preventDefault();const button=login.querySelector('button');button.disabled=true;const error=login.querySelector('.form-error');error.textContent='';try{const r=await fetch('/api/login',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(Object.fromEntries(new FormData(login)))});const result=await r.json();if(!r.ok)throw new Error(result.error);location.reload();}catch(e){error.textContent=e.message;}finally{button.disabled=false;}});
const portal=document.querySelector('.portal');
if(portal){
 const csrf=portal.dataset.csrf,dialog=document.querySelector('#editor'),form=document.querySelector('#listing-form'),manager=document.querySelector('#listing-manager');let listings=[],images=[],floorplan='';
 async function api(url,method='GET',data){const r=await fetch(url,{method,headers:{'Content-Type':'application/json','X-CSRF-Token':csrf},...(data?{body:JSON.stringify(data)}:{})});const b=await r.json();if(r.status===401){location.reload();throw new Error('Your session expired.');}if(!r.ok)throw new Error(b.error);return b;}
 function message(s){document.querySelector('#portal-message').textContent=s;}
 const statusLabel=p=>({'draft':'Draft · private','published':'Published · available','sold':'Sold · visible','sold-hidden':'Sold · hidden'}[p.status]);
 function render(){const query=document.querySelector('#property-search').value.toLowerCase(),status=document.querySelector('#status-filter').value;const filtered=listings.filter(p=>(status==='all'||p.status===status)&&[p.title,p.area,p.reference].join(' ').toLowerCase().includes(query));manager.innerHTML=filtered.length?filtered.map(p=>`<article class="manager-row">${p.images[0]?`<img src="${esc(p.images[0])}" alt="">`:'<span class="placeholder-image">Photo</span>'}<div><span class="status-pill ${p.status.startsWith('sold')?'status-sold':''}">${statusLabel(p)}</span><h3>${esc(p.title)}</h3><p>${esc(p.area)} · ${p.type==='off-plan'?'Off-plan':'Secondary'} · AED ${Number(p.price).toLocaleString('en-AE')}</p></div><div class="manager-actions"><button data-action="edit" data-id="${esc(p.id)}">Edit advert</button><a href="/property/${esc(p.id)}" target="_blank" rel="noopener">Full profile</a><a href="/property/${esc(p.id)}?advert" target="_blank" rel="noopener">PDF brochure</a><button data-action="sold" data-id="${esc(p.id)}">${p.status.startsWith('sold')?'Change sold status':'Mark as sold'}</button><button class="delete" data-action="delete" data-id="${esc(p.id)}">Delete</button></div></article>`).join(''):`<div class="empty-listings"><h3>${listings.length?'No matching properties.':'Your first advert starts here.'}</h3><p>${listings.length?'Try another search or status filter.':'Choose a template above. Build your property profile, save a draft, then publish when it’s ready.'}</p></div>`;}
 async function refresh(){try{listings=await api('/api/listings');render();}catch(e){message(e.message);}}
 document.querySelector('#property-search').addEventListener('input',render);document.querySelector('#status-filter').addEventListener('change',render);
 function showPhotos(){document.querySelector('#photo-preview').innerHTML=images.map((src,i)=>`<div><img src="${esc(src)}" alt="Property photo ${i+1}"><span>${i===0?'Cover':i+1}</span><div class="photo-controls"><button type="button" data-move="${i}" data-dir="-1" ${i===0?'disabled':''} aria-label="Move photo ${i+1} earlier"><svg class="arrow-icon arrow-left" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M21 12H3m6-6-6 6 6 6"/></svg></button><button type="button" data-move="${i}" data-dir="1" ${i===images.length-1?'disabled':''} aria-label="Move photo ${i+1} later"><svg class="arrow-icon arrow-right" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M3 12h18m-6-6 6 6-6 6"/></svg></button><button type="button" data-remove="${i}" aria-label="Remove photo ${i+1}">×</button></div></div>`).join('');document.querySelector('#floorplan-preview').innerHTML=floorplan?`<img src="${esc(floorplan)}" alt="Floor plan preview"><button type="button" id="remove-floorplan">Remove floor plan</button>`:'';document.querySelector('#remove-floorplan')?.addEventListener('click',()=>{floorplan='';showPhotos();});}
 function edit(type,p){form.reset();form.querySelector('.form-error').textContent='';for(const [k,v]of Object.entries(p||{type,id:'',status:'draft'})){if(form.elements.namedItem(k))form.elements.namedItem(k).value=v;}form.elements.type.value=type;images=[...(p?.images||[])];floorplan=p?.floorplan||'';document.querySelector('.offplan-fields').hidden=type!=='off-plan';document.querySelector('.secondary-fields').hidden=type==='off-plan';document.querySelector('#template-label').textContent=type==='off-plan'?'OFF-PLAN TEMPLATE':'SECONDARY SALES TEMPLATE';document.querySelector('#editor-title').textContent=p?'Edit your advert':'Create an advert';showPhotos();dialog.showModal();}
 document.querySelectorAll('.new-listing').forEach(b=>b.addEventListener('click',()=>edit(b.dataset.type)));
 manager.addEventListener('click',async e=>{const b=e.target.closest('button[data-action]');if(!b)return;const p=listings.find(p=>p.id===b.dataset.id);if(b.dataset.action==='edit')edit(p.type,p);else if(b.dataset.action==='sold'){edit(p.type,p);form.elements.status.value=p.status.startsWith('sold')?p.status:'sold';form.elements.status.focus();}else if(confirm(`Delete “${p.title}”? This removes it from the website.`)){try{await api('/api/listings/'+p.id,'DELETE');message('Advert deleted.');await refresh();}catch(e){message(e.message);}}});
 ['close-editor','cancel-editor'].forEach(id=>document.querySelector('#'+id).addEventListener('click',()=>dialog.close()));
 document.querySelector('#photo-preview').addEventListener('click',e=>{const b=e.target.closest('[data-remove]');if(b){images.splice(Number(b.dataset.remove),1);showPhotos();}else{const move=e.target.closest('[data-move]');if(move){const from=Number(move.dataset.move),to=from+Number(move.dataset.dir);[images[from],images[to]]=[images[to],images[from]];showPhotos();}}});
 let uploading=false;
 async function uploadFiles(e,isFloorplan=false){if(!isFloorplan&&images.length+e.target.files.length>12){form.querySelector('.form-error').textContent='Use up to 12 property photos.';e.target.value='';return;}uploading=true;const submit=form.querySelector('[type=submit]');submit.disabled=true;form.querySelectorAll('input[type=file]').forEach(x=>x.disabled=true);form.querySelector('.form-error').textContent='Uploading images…';try{for(const file of e.target.files){if(file.size>10*1024*1024)throw new Error('Images must be smaller than 10 MB.');const image=await new Promise((resolve,reject)=>{const reader=new FileReader();reader.onload=()=>resolve(reader.result);reader.onerror=()=>reject(new Error('Could not read this image.'));reader.readAsDataURL(file);});const url=(await api('/api/upload','POST',{image})).url;if(isFloorplan)floorplan=url;else images.push(url);showPhotos();}form.querySelector('.form-error').textContent='';}catch(err){form.querySelector('.form-error').textContent=err.message;}finally{uploading=false;submit.disabled=false;form.querySelectorAll('input[type=file]').forEach(x=>x.disabled=false);e.target.value='';}}
 document.querySelector('#photos').addEventListener('change',e=>uploadFiles(e));document.querySelector('#floorplan-upload').addEventListener('change',e=>uploadFiles(e,true));
 form.addEventListener('submit',async e=>{e.preventDefault();if(uploading)return;const b=form.querySelector('[type=submit]');b.disabled=true;try{const p=await api('/api/listings','POST',{...Object.fromEntries(new FormData(form)),images,floorplan});dialog.close();message({'published':'Advert published on your website.','draft':'Draft saved.','sold':'Marked as sold. The profile remains on your website.','sold-hidden':'Marked as sold and hidden from your website.'}[p.status]);await refresh();}catch(e){form.querySelector('.form-error').textContent=e.message;}finally{b.disabled=false;}});
 document.querySelector('#logout').addEventListener('click',async()=>{try{await api('/api/logout','POST',{});location.reload();}catch(e){message(e.message);}});
 refresh();
}

const passwordForm=document.querySelector('#password-form');
passwordForm?.addEventListener('submit',async e=>{e.preventDefault();const button=passwordForm.querySelector('button'),error=passwordForm.querySelector('.form-error');button.disabled=true;error.textContent='';try{const r=await fetch('/api/password',{method:'POST',headers:{'Content-Type':'application/json','X-CSRF-Token':passwordForm.dataset.csrf},body:JSON.stringify(Object.fromEntries(new FormData(passwordForm)))});const result=await r.json();if(!r.ok)throw new Error(result.error);location.assign('/portal');}catch(e){error.textContent=e.message;}finally{button.disabled=false;}});

const passkeyLogin=document.querySelector('#passkey-login');
async function passkeyRequest(url,data,csrf){const r=await fetch(url,{method:'POST',headers:{'Content-Type':'application/json',...(csrf?{'X-CSRF-Token':csrf}:{})},body:JSON.stringify(data||{})});const value=await r.json();if(!r.ok)throw new Error(value.error);return value;}
function passkeyError(error){return error.name==='NotAllowedError'?'Passkey request cancelled. You can try again or use your password.':error.message;}
if(passkeyLogin){if(!window.PublicKeyCredential||!isSecureContext)passkeyLogin.hidden=true;passkeyLogin.addEventListener('click',async()=>{passkeyLogin.disabled=true;const error=login.querySelector('.form-error');error.textContent='';try{const optionsJSON=await passkeyRequest('/api/passkeys/auth-options');const result=await SimpleWebAuthnBrowser.startAuthentication({optionsJSON});await passkeyRequest('/api/passkeys/auth-verify',result);location.reload();}catch(e){error.textContent=passkeyError(e);}finally{passkeyLogin.disabled=false;}});}
const passkeySetup=document.querySelector('#passkey-setup');
if(passkeySetup){const csrf=portal.dataset.csrf,error=passkeySetup.querySelector('.form-error'),list=document.querySelector('#passkey-list');async function refreshPasskeys(){const r=await fetch('/api/passkeys');if(!r.ok)throw new Error('Please sign in again.');const keys=await r.json();list.innerHTML=keys.length?keys.map(k=>`<div class="passkey-row"><span>${esc(k.name)}</span><button type="button" data-remove-key="${esc(k.id)}">Remove</button></div>`).join(''):'<p class="field-hint">No passkeys added yet.</p>';}
passkeySetup.addEventListener('submit',async e=>{e.preventDefault();const button=passkeySetup.querySelector('button');button.disabled=true;error.textContent='';try{if(!window.PublicKeyCredential||!isSecureContext)throw new Error('Use a browser that supports passkeys over HTTPS.');const optionsJSON=await passkeyRequest('/api/passkeys/register-options',Object.fromEntries(new FormData(passkeySetup)),csrf);const result=await SimpleWebAuthnBrowser.startRegistration({optionsJSON});await passkeyRequest('/api/passkeys/register-verify',result,csrf);passkeySetup.reset();error.textContent='Passkey added. You can now use it to sign in.';await refreshPasskeys();}catch(e){error.textContent=passkeyError(e);}finally{button.disabled=false;}});
list.addEventListener('click',async e=>{const button=e.target.closest('[data-remove-key]');if(!button)return;const password=passkeySetup.elements.password.value;if(!password){error.textContent='Enter your current password below to remove this passkey.';passkeySetup.elements.password.focus();return;}button.disabled=true;try{await passkeyRequest('/api/passkeys/remove',{id:button.dataset.removeKey,password},csrf);location.reload();}catch(e){error.textContent=e.message;button.disabled=false;}});refreshPasskeys().catch(e=>error.textContent=e.message);}

#password-form[hidden],.passkey-offer[hidden]{display:none}.passkey-offer>p{font-size:14px;color:var(--muted)}.passkey-offer>.button{width:100%;margin-top:14px}.passkey-offer .eyebrow{font-size:10px;color:#80613c}.passkey-offer h2{margin-top:18px}

.passkey-offer>.button[hidden]{display:none}
