// ── Shared search engine + result components (all 3 variations) ──
const { Icon, Photo, HeartBtn, fmtPrice } = window;

// Filter model: { deal, priceMin, priceMax, beds, baths, types[], hoods[], features[], video }
function passes(l, f) {
  if (f.deal && f.deal !== 'all' && l.deal !== f.deal) return false;
  if (f.priceMin != null && l.price < f.priceMin) return false;
  if (f.priceMax != null && l.price > f.priceMax) return false;
  if (f.beds && l.beds < f.beds) return false;
  if (f.baths && l.baths < f.baths) return false;
  if (f.types && f.types.length && !f.types.includes(l.type)) return false;
  if (f.hoods && f.hoods.length && !f.hoods.includes(l.hood)) return false;
  if (f.video && !l.video) return false;
  if (f.features && f.features.length) {
    const ll = l.features.map(x => x.toLowerCase());
    if (!f.features.every(ft => ll.some(x => x.includes(ft.toLowerCase())))) return false;
  }
  return true;
}

function filterListings(list, f) { return list.filter(l => passes(l, f)); }

// Soft relevance score (0–1) + human reasons — used for ranking & match %
function score(l, f) {
  let pts = 0, max = 0; const reasons = [];
  const add = (ok, w, label) => { max += w; if (ok) { pts += w; if (label) reasons.push(label); } };
  if (f.deal && f.deal !== 'all') add(l.deal === f.deal, 2, l.deal === 'rent' ? 'For rent' : 'For sale');
  if (f.priceMax != null) add(l.price <= f.priceMax, 3, l.price <= f.priceMax ? 'Within budget' : null);
  if (f.beds) add(l.beds >= f.beds, 2, l.beds >= f.beds ? `${l.beds} bedrooms` : null);
  if (f.baths) add(l.baths >= f.baths, 1, null);
  if (f.types && f.types.length) add(f.types.includes(l.type), 2, f.types.includes(l.type) ? l.type : null);
  if (f.hoods && f.hoods.length) add(f.hoods.includes(l.hood), 2, f.hoods.includes(l.hood) ? l.hood : null);
  if (f.video) add(l.video, 1, l.video ? 'Video tour' : null);
  if (f.features && f.features.length) {
    const ll = l.features.map(x => x.toLowerCase());
    f.features.forEach(ft => add(ll.some(x => x.includes(ft.toLowerCase())), 1.5,
      ll.some(x => x.includes(ft.toLowerCase())) ? ft : null));
  }
  const pct = max === 0 ? 0.82 + (l.id.charCodeAt(1) % 18) / 100 : 0.55 + 0.45 * (pts / max);
  return { pct: Math.min(0.99, pct), reasons: reasons.slice(0, 3) };
}

function rankListings(list, f) {
  return [...list].map(l => ({ l, ...score(l, f) }))
    .sort((a, b) => b.pct - a.pct);
}

// Compact result row
function ResultRow({ l, saved, onToggleSave, match, reasons, onClick }) {
  return (
    <div onClick={onClick} style={{ display: 'flex', gap: 13, padding: '12px 0', cursor: onClick ? 'pointer' : 'default',
      borderBottom: '1px solid var(--line)' }}>
      <div style={{ position: 'relative', width: 104, height: 104, borderRadius: 14, overflow: 'hidden', flexShrink: 0 }}>
        <Photo hue={l.hue} kind={l.type} label={false} />
        {l.video && (
          <span style={{ position: 'absolute', bottom: 7, left: 7, width: 24, height: 24, borderRadius: 999,
            background: 'rgba(20,20,18,0.6)', backdropFilter: 'blur(6px)', display: 'flex', alignItems: 'center',
            justifyContent: 'center' }}><Icon name="play" size={11} s="#fff" fill="#fff" /></span>
        )}
      </div>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 8 }}>
          <span style={{ fontFamily: 'var(--sans)', fontSize: 19, fontWeight: 700, letterSpacing: -0.5,
            color: 'var(--ink)' }}>{fmtPrice(l)}</span>
          {match != null
            ? <MatchPill pct={match} />
            : <HeartBtn active={saved} onClick={() => onToggleSave(l.id)} size={32} />}
        </div>
        <div style={{ marginTop: 3, fontFamily: 'var(--sans)', fontSize: 13.5, color: 'var(--ink)', fontWeight: 500,
          whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{l.address}</div>
        <div style={{ marginTop: 1, fontFamily: 'var(--sans)', fontSize: 12.5, color: 'var(--muted)' }}>
          {l.beds} bd · {l.baths} ba · {l.sqft.toLocaleString()} sqft · {l.hood}</div>
        {reasons && reasons.length > 0 && (
          <div style={{ marginTop: 8, display: 'flex', flexWrap: 'wrap', gap: 5 }}>
            {reasons.map(r => (
              <span key={r} style={{ display: 'inline-flex', alignItems: 'center', gap: 4, height: 24, padding: '0 8px',
                borderRadius: 999, background: 'var(--accent-soft)', fontFamily: 'var(--sans)', fontSize: 11,
                fontWeight: 600, color: 'var(--accent)' }}>
                <Icon name="check" size={11} s="var(--accent)" sw={2.6} />{r}</span>
            ))}
          </div>
        )}
      </div>
    </div>
  );
}

function MatchPill({ pct }) {
  const n = Math.round(pct * 100);
  return (
    <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5, height: 26, padding: '0 10px',
      borderRadius: 999, background: 'var(--accent)', flexShrink: 0 }}>
      <Icon name="sparkle" size={13} s="#fff" />
      <span style={{ fontFamily: 'var(--sans)', fontSize: 12.5, fontWeight: 700, color: '#fff' }}>{n}%</span>
    </span>
  );
}

// Tiny count + sort bar
function CountBar({ n, sort, setSort, sortLabel }) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '4px 0 2px' }}>
      <span style={{ fontFamily: 'var(--sans)', fontSize: 14.5, fontWeight: 700, color: 'var(--ink)', whiteSpace: 'nowrap' }}>
        {n} {n === 1 ? 'home' : 'homes'}</span>
      {setSort ? (
        <button onClick={setSort} style={{ display: 'inline-flex', alignItems: 'center', gap: 5, height: 30,
          padding: '0 4px', border: 'none', background: 'none', cursor: 'pointer', fontFamily: 'var(--sans)',
          fontSize: 13, fontWeight: 600, color: 'var(--muted)', whiteSpace: 'nowrap' }}>
          <Icon name="arrowUp" size={14} s="var(--muted)" />{sortLabel}</button>
      ) : sortLabel ? (
        <span style={{ fontFamily: 'var(--sans)', fontSize: 13, color: 'var(--muted)', whiteSpace: 'nowrap' }}>{sortLabel}</span>
      ) : null}
    </div>
  );
}

function NoResults() {
  return (
    <div style={{ textAlign: 'center', padding: '48px 30px' }}>
      <div style={{ width: 58, height: 58, borderRadius: 999, background: 'oklch(0.94 0.004 95)', display: 'flex',
        alignItems: 'center', justifyContent: 'center', margin: '0 auto 14px' }}>
        <Icon name="search" size={26} s="var(--muted)" /></div>
      <div style={{ fontFamily: 'var(--sans)', fontSize: 16, fontWeight: 600, color: 'var(--ink)' }}>Nothing matches yet</div>
      <div style={{ fontFamily: 'var(--sans)', fontSize: 13.5, color: 'var(--muted)', marginTop: 3 }}>
        Loosen a filter to see more homes.</div>
    </div>
  );
}

Object.assign(window, { filterListings, rankListings, score, ResultRow, MatchPill, CountBar, NoResults,
  parseNum, parseQuery, fmtMoney, chipsFromFilters });

// ── Natural-language parser (shared by AI search + canvas demo) ──
function parseNum(raw) {
  if (!raw) return null;
  let s = String(raw).toLowerCase().replace(/[$,\s]/g, '');
  let mult = 1;
  if (s.endsWith('k')) { mult = 1e3; s = s.slice(0, -1); }
  else if (s.endsWith('m')) { mult = 1e6; s = s.slice(0, -1); }
  const n = parseFloat(s);
  return isNaN(n) ? null : Math.round(n * mult);
}

function parseQuery(q) {
  const NB = window.NEIGHBORHOODS || [], PT = window.PROPERTY_TYPES || [];
  const t = ' ' + q.toLowerCase() + ' ';
  const f = { deal: null, priceMin: null, priceMax: null, beds: 0, baths: 0, types: [], hoods: [], features: [], video: false };
  if (/\b(rent|rental|lease|renting|to rent)\b/.test(t)) f.deal = 'rent';
  if (/\b(buy|sale|purchase|buying|to buy|for sale|own)\b/.test(t)) f.deal = 'buy';
  const bed = t.match(/(\d+)\s*(?:bed|bd|br|bedroom)/); if (bed) f.beds = +bed[1];
  const bath = t.match(/(\d+)\s*(?:bath|ba|bathroom)/); if (bath) f.baths = +bath[1];
  const numRe = '\\$?\\d[\\d,\\.]*\\s*[km]?';
  const under = t.match(new RegExp('(?:under|below|less than|max|up to|cheaper than|<)\\s*(' + numRe + ')'));
  if (under) f.priceMax = parseNum(under[1]);
  const over = t.match(new RegExp('(?:over|above|from|min|at least|>)\\s*(' + numRe + ')'));
  if (over) f.priceMin = parseNum(over[1]);
  NB.forEach(h => { if (t.includes(h.toLowerCase())) f.hoods.push(h); });
  if (/\bbeach|ocean\b/.test(t) && !f.hoods.length) f.hoods.push('Sunset');
  PT.forEach(ty => { if (t.includes(ty.toLowerCase())) f.types.push(ty); });
  if (/\b(home|house|family home|single family)\b/.test(t) && !f.types.includes('House')) f.types.push('House');
  const featMap = [['Parking', /park/], ['Garden', /yard|garden|outdoor/], ['Laundry', /laundry|washer/],
    ['Pet friendly', /pet|dog|cat/], ['Roof deck', /roof/], ['Elevator', /elevator|lift/], ['City views', /view/]];
  featMap.forEach(([label, re]) => { if (re.test(t)) f.features.push(label); });
  if (/\b(video|tour|reel|walkthrough)\b/.test(t)) f.video = true;
  return f;
}

function fmtMoney(n) {
  if (n == null) return '';
  return n >= 1e6 ? '$' + (n / 1e6).toFixed(2).replace(/0+$/, '').replace(/\.$/, '') + 'M'
    : n >= 1000 ? '$' + (n / 1000).toFixed(1).replace(/\.0$/, '') + 'k' : '$' + n;
}

function chipsFromFilters(f) {
  const c = [];
  if (f.deal) c.push({ key: 'deal', label: f.deal === 'rent' ? 'For rent' : 'For sale' });
  if (f.beds) c.push({ key: 'beds', label: `${f.beds}+ beds` });
  if (f.baths) c.push({ key: 'baths', label: `${f.baths}+ baths` });
  if (f.priceMax != null) c.push({ key: 'priceMax', label: `Under ${fmtMoney(f.priceMax)}` });
  if (f.priceMin != null) c.push({ key: 'priceMin', label: `Over ${fmtMoney(f.priceMin)}` });
  (f.types || []).forEach(t => c.push({ key: 'type:' + t, label: t }));
  (f.hoods || []).forEach(h => c.push({ key: 'hood:' + h, label: h }));
  (f.features || []).forEach(ft => c.push({ key: 'feat:' + ft, label: ft }));
  if (f.video) c.push({ key: 'video', label: 'Video tour' });
  return c;
}
