const $=id=>document.getElementById(id); const esc=v=>String(v??"").replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',"'":'''}[c]));
let prospects=[],runs=[],settings={},selected=null,pollTimer=null;
async function api(path,opt={}){const r=await fetch(path,{...opt,headers:{'Content-Type':'application/json',...(opt.headers||{})}});const b=await r.json().catch(()=>({}));if(!r.ok)throw Error(b.error||`Request failed (${r.status})`);return b}
function date(v){if(!v)return'—';const d=new Date(v);return isNaN(d)?String(v):d.toLocaleString(undefined,{day:'numeric',month:'short',hour:'2-digit',minute:'2-digit'})}
function layout(title,sub,body,actions=''){ $('pageName').textContent=title; return `
PROSPECTOS
${title}
${sub}
${actions}
${body}` }
function empty(text){return `
⌕${text}
`}
function scoreClass(n){return n>=75?'hot':n>=55?'warm':'cool'}
function prospectRows(){const q=($('prospectSearch')?.value||'').toLowerCase();const st=$('prospectStatus')?.value||'';return prospects.filter(p=>{const hay=[p.name,p.domain,p.category,p.address,p.emails].join(' ').toLowerCase();return(!q||hay.includes(q))&&(!st||p.review_status===st)})}
function prospectTable(){const rows=prospectRows();return `
${rows.length?'':empty('No prospects match these filters')}`}
function renderDashboard(){const total=prospects.length,pending=prospects.filter(p=>p.review_status==='pending').length,hot=prospects.filter(p=>p.opportunity_score>=75).length,withEmail=prospects.filter(p=>p.emails).length;const latest=runs[0];$('view').innerHTML=layout('Dashboard','A clear view of what was found, what needs review, and where the best opportunities are.',`
CATALOGUE${total}saved prospects
NEEDS REVIEW${pending}awaiting your decision
HIGH OPPORTUNITY${hot}score 75 or higher
PUBLIC EMAILS${withEmail}evidence-backed contacts
NEXT ACTION
Review your strongest leads
Inspect the evidence before deciding whether to contact them.
`)}
function renderDiscover(){ $('view').innerHTML=layout('Discover','Set the criteria for this run. Source configuration and provider settings live in Settings.',`
NEW RUN
Find potential customers
Search Google Maps, deduplicate the catalogue, then enrich each result.
Email collection always on
HOW IT WORKS
Evidence-first pipeline
SearchMaps worker finds public business listings.
EnrichWebsite, HTTPS, DNS hints and public email checks run safely.
ReviewYou decide what is relevant before any contact.
`)}
function renderProspects(){ $('view').innerHTML=layout('Prospects','Search, filter and review your catalogue. Click a row to inspect the evidence.',`
⌕
${prospectTable()}
`)}
function renderRuns(){ $('view').innerHTML=layout('Runs','Monitor discovery jobs and inspect errors without leaving the run history.',`
Run
Status
Results
Started
Source
${runs.map(r=>`
#${r.id}${esc(r.query)}
${esc(r.status)}${esc(r.stage||'')}
${r.added||0} added / ${r.found||0} found
${date(r.created_at)}
${esc(r.source||'google_maps')}
`).join('')}
${runs.length?'':empty('No discovery runs yet')}`)}
function renderSettings(){const ai=settings.ai||{};const sch=settings.schedules||[];const sup=settings.suppressions||[];$('view').innerHTML=layout('Settings','Configure providers, automation and safety controls here. These settings are separate from daily discovery.',`
SOURCE
Google Maps worker
The bundled standard gosom worker runs locally and does not require an API key.