#!/usr/bin/env node
/**
 * Reonomy Scraper v14 - Self-Configuring Search + Extract
 * 
 * Sets up search filters IN THE SAME browser session, then scrapes.
 * No more search ID handoff problem.
 * 
 * ENV CONFIG:
 *   REONOMY_STATE    - Location filter (e.g., "New Jersey")
 *   REONOMY_TYPES    - Comma-separated property types (e.g., "Industrial")
 *   REONOMY_MIN_SF   - Min building area in SF (e.g., "50000")
 *   REONOMY_SALE_FILTER - "not_within_10y", "not_within_5y", "not_within_2y", "within_10y", etc.
 *   REONOMY_OWNER_PHONE - "true" to require phone
 *   REONOMY_OWNER_EMAIL - "true" to require email
 *   MAX_PROPERTIES   - Max properties to scrape (default 20)
 *   HEADLESS         - "false" to show browser
 */

const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');

const CONFIG = {
  authStatePath: path.join(process.env.HOME, '.clawdbot/workspace/reonomy-auth.json'),
  outputPath: path.join(process.env.HOME, '.clawdbot/workspace/reonomy-leads-v14.json'),
  logPath: path.join(process.env.HOME, '.clawdbot/workspace/reonomy-scraper-v14.log'),
  dailyLogPath: path.join(process.env.HOME, '.clawdbot/workspace/reonomy-daily-stats.json'),
  maxProperties: parseInt(process.env.MAX_PROPERTIES) || 20,
  maxDailyProperties: 50,
  email: process.env.REONOMY_EMAIL || 'henry@realestateenhanced.com',
  password: process.env.REONOMY_PASSWORD || '9082166532',
  // Search filters
  searchState: process.env.REONOMY_STATE || 'New Jersey',
  propertyTypes: (process.env.REONOMY_TYPES || 'Industrial').split(',').map(s => s.trim()),
  minSF: process.env.REONOMY_MIN_SF || '',
  saleFilter: process.env.REONOMY_SALE_FILTER || '', // e.g., "not_within_10y"
  ownerPhone: process.env.REONOMY_OWNER_PHONE === 'true',
  ownerEmail: process.env.REONOMY_OWNER_EMAIL === 'true',
};

function log(msg) {
  const timestamp = new Date().toISOString();
  const line = `[${timestamp}] ${msg}`;
  console.log(line);
  fs.appendFileSync(CONFIG.logPath, line + '\n');
}

function ab(cmd, options = {}) {
  const fullCmd = `agent-browser ${cmd}`;
  if (options.verbose !== false) log(`  > ${fullCmd.substring(0, 120)}`);
  try {
    const result = execSync(fullCmd, {
      encoding: 'utf8',
      timeout: options.timeout || 30000,
      stdio: ['pipe', 'pipe', 'pipe']
    });
    return { success: true, output: result.trim() };
  } catch (err) {
    const stderr = err.stderr?.toString() || err.message;
    if (options.verbose !== false) log(`  ! Error: ${stderr.substring(0, 200)}`);
    return { success: false, error: stderr };
  }
}

function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }

function randomDelay(min, max) {
  return sleep(Math.floor(Math.random() * (max - min + 1)) + min);
}

function shuffle(arr) {
  const a = [...arr];
  for (let i = a.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [a[i], a[j]] = [a[j], a[i]];
  }
  return a;
}

function getDailyStats() {
  const today = new Date().toISOString().split('T')[0];
  try {
    const data = JSON.parse(fs.readFileSync(CONFIG.dailyLogPath, 'utf8'));
    if (data.date === today) return data;
  } catch (e) {}
  return { date: today, propertiesScraped: 0, leadsFound: 0 };
}

function saveDailyStats(stats) {
  fs.writeFileSync(CONFIG.dailyLogPath, JSON.stringify(stats, null, 2));
}

// ── LOGIN ──
async function login() {
  log('  Navigating to login...');
  ab('open "https://app.reonomy.com/#!/login"');
  await sleep(4000);

  const snap = ab('snapshot -i');
  if (!snap.output?.includes('textbox "Email"')) {
    const url = ab('eval "window.location.href"');
    if (url.output?.includes('app.reonomy.com') && !url.output?.includes('login') && !url.output?.includes('auth.reonomy.com')) {
      log('  Already logged in!');
      return true;
    }
    throw new Error('Login form not found');
  }

  const emailRef = snap.output.match(/textbox "Email" \[ref=(e\d+)\]/)?.[1];
  const passRef = snap.output.match(/textbox "Password" \[ref=(e\d+)\]/)?.[1];
  const loginRef = snap.output.match(/button "Log In" \[ref=(e\d+)\]/)?.[1];
  if (!emailRef || !passRef || !loginRef) throw new Error('Login form elements not found');

  ab(`fill @${emailRef} "${CONFIG.email}"`);
  await sleep(1000);
  ab(`fill @${passRef} "${CONFIG.password}"`);
  await sleep(1000);
  ab(`click @${loginRef}`);
  await randomDelay(12000, 16000);

  const postUrl = ab('eval "window.location.href"');
  if (postUrl.output?.includes('auth.reonomy.com') || postUrl.output?.includes('login')) {
    throw new Error('Login failed');
  }

  ab(`state save "${CONFIG.authStatePath}"`);
  log('  Login successful!');
  return true;
}

// ── BUILD SEARCH ──
async function buildSearch() {
  log('\n== Building search with filters ==');

  // Go to search page via Advanced Search
  ab('open "https://app.reonomy.com/#!/search"');
  await sleep(5000);

  // Dismiss banners
  for (let i = 0; i < 5; i++) {
    const s = ab('snapshot -i', { verbose: false });
    const lg = s.output?.match(/button "Let's Go" \[ref=(e\d+)\]/);
    if (!lg) break;
    ab(`click @${lg[1]}`, { verbose: false });
    await sleep(800);
  }

  // Click Advanced Search if visible
  let snap = ab('snapshot -i');
  const advRef = snap.output?.match(/link "Advanced Search" \[ref=(e\d+)\]/)?.[1];
  if (advRef) {
    ab(`click @${advRef}`);
    await sleep(3000);
  }

  // ── Location ──
  log(`  Setting location: ${CONFIG.searchState}`);
  
  // Take FRESH snapshot after Advanced Search click (refs changed)
  snap = ab('snapshot -i');
  let searchBox = snap.output?.match(/textbox "(?:Search by address, location, or owner|Address, Location, or Owner)" \[ref=(e\d+)\]/)?.[1];
  if (!searchBox) throw new Error('Search box not found');

  // Use fill to set the text, then manually trigger the autocomplete
  ab(`fill @${searchBox} "${CONFIG.searchState}"`);
  await sleep(3000);

  // Retry up to 4 times to find the state suggestion
  let stateRef = null;
  for (let attempt = 0; attempt < 4 && !stateRef; attempt++) {
    snap = ab('snapshot -i');
    stateRef = snap.output?.match(new RegExp(`menuitem "${CONFIG.searchState}" \\[ref=(e\\d+)\\]`))?.[1];
    if (!stateRef) {
      log(`  State not found (attempt ${attempt + 1}), retrying...`);
      // Re-find search box (ref might have changed)
      searchBox = snap.output?.match(/textbox "(?:Search by address|Address)[^"]*" \[ref=(e\d+)\]/)?.[1]
                || snap.output?.match(/textbox "[^"]*" \[ref=(e\d+)\]/)?.[1];
      if (searchBox) {
        // Clear and retype
        ab(`fill @${searchBox} ""`);
        await sleep(500);
        ab(`fill @${searchBox} "${CONFIG.searchState}"`);
        await sleep(3000);
      } else {
        await sleep(2000);
      }
    }
  }
  
  if (!stateRef) throw new Error(`State "${CONFIG.searchState}" not found in suggestions`);
  
  ab(`click @${stateRef}`);
  await sleep(6000);

  // ── Property Type ──
  log(`  Setting property types: ${CONFIG.propertyTypes.join(', ')}`);
  snap = ab('snapshot -i');
  const ptRef = snap.output?.match(/button "Property Type" \[ref=(e\d+)\]/)?.[1];
  if (!ptRef) throw new Error('Property Type button not found');
  ab(`click @${ptRef}`);
  await sleep(2000);

  snap = ab('snapshot -i');
  for (const ptype of CONFIG.propertyTypes) {
    const cbRef = snap.output?.match(new RegExp(`checkbox "${ptype}" \\[ref=(e\\d+)\\]`))?.[1];
    if (cbRef) {
      ab(`click @${cbRef}`);
      await sleep(500);
    } else {
      log(`  ! Property type "${ptype}" not found in quick list, checking See All...`);
      // Try See All Property Types
      const seeAllRef = snap.output?.match(/button "See All Property Types" \[ref=(e\d+)\]/)?.[1];
      if (seeAllRef) {
        ab(`click @${seeAllRef}`);
        await sleep(2000);
        snap = ab('snapshot -i');
        const cbRef2 = snap.output?.match(new RegExp(`checkbox "${ptype}" \\[ref=(e\\d+)\\]`))?.[1];
        if (cbRef2) {
          ab(`click @${cbRef2}`);
          await sleep(500);
        } else {
          log(`  ! Could not find "${ptype}" checkbox`);
        }
      }
    }
  }

  // Click Apply for property type
  snap = ab('snapshot -i');
  const ptApply = snap.output?.match(/button "Apply" \[ref=(e\d+)\]/)?.[1];
  if (ptApply) {
    ab(`click @${ptApply}`);
    await sleep(5000);
  }

  // ── Building Area (SF) ──
  if (CONFIG.minSF) {
    log(`  Setting min building area: ${CONFIG.minSF} SF`);
    snap = ab('snapshot -i');
    const sizeRef = snap.output?.match(/button "Size" \[ref=(e\d+)\]/)?.[1];
    if (sizeRef) {
      ab(`click @${sizeRef}`);
      await sleep(2000);

      // Click the Building Area min dropdown button to open presets
      // The full snapshot shows: Building Area (SF) with a button containing textbox "min"
      // We need the SECOND button with textbox "min" (first is Total Units)
      snap = ab('snapshot -i');

      // Find the second min textbox (Building Area)
      const minRefs = [...(snap.output?.matchAll(/textbox "min" \[ref=(e\d+)\]/g) || [])];
      if (minRefs.length >= 2) {
        const areaMinRef = minRefs[1][1];
        // Click the field to open dropdown
        ab(`click @${areaMinRef}`);
        await sleep(1500);

        // Type the value
        ab(`type @${areaMinRef} "${CONFIG.minSF}"`);
        await sleep(1000);

        // Check for preset dropdown option (e.g., "50k sf")
        snap = ab('snapshot -i');
        const sfK = Math.round(parseInt(CONFIG.minSF) / 1000);
        const presetPatterns = [`${sfK}k sf`, `${CONFIG.minSF}`, `${sfK},000`];
        let presetClicked = false;
        for (const pat of presetPatterns) {
          const presetRef = snap.output?.match(new RegExp(`(?:menuitem|option|button|listitem) "${pat}[^"]*" \\[ref=(e\\d+)\\]`, 'i'))?.[1];
          if (presetRef) {
            log(`  Clicking preset: ${pat}`);
            ab(`click @${presetRef}`);
            presetClicked = true;
            await sleep(2000);
            break;
          }
        }

        if (!presetClicked) {
          // Press Enter to commit the typed value
          log('  No preset found, pressing Enter to commit');
          ab(`eval "document.querySelectorAll('input[placeholder=\\"min\\"]')[1]?.dispatchEvent(new KeyboardEvent('keydown', {key: 'Enter', keyCode: 13, bubbles: true}))"`);
          await sleep(1000);
        }
      }

      // Now try to click Apply
      await sleep(1000);
      snap = ab('snapshot -i');
      const sizeApply = snap.output?.match(/button "Apply" \[ref=(e\d+)\](?!\s*\[disabled\])/)?.[1];
      if (sizeApply) {
        ab(`click @${sizeApply}`);
        await sleep(5000);
      } else {
        // Check if filter tag already shows (e.g., "50000+ SF")
        const filterTag = snap.output?.match(/button "\d+.*SF" \[ref=(e\d+)\]/);
        if (filterTag) {
          log('  Size filter appears applied via tag');
        } else {
          // Try pressing Escape then check
          log('  Pressing Escape to close size panel');
          ab('eval "document.dispatchEvent(new KeyboardEvent(\'keydown\', {key: \'Escape\', bubbles: true}))"');
          await sleep(2000);
        }
      }
    }

    // Verify size filter is applied
    snap = ab('snapshot -i');
    const sfTag = snap.output?.match(/button "\d+.*SF" \[ref=(e\d+)\]/);
    if (sfTag) {
      log(`  Size filter confirmed: ${sfTag[0]}`);
    } else {
      log('  WARNING: Size filter may not be applied');
    }
  }

  // ── Sale Date Filter ──
  if (CONFIG.saleFilter) {
    log(`  Setting sale filter: ${CONFIG.saleFilter}`);
    // Make sure no dropdowns are blocking
    await sleep(1000);
    snap = ab('snapshot -i');
    
    // Close any open panels first by clicking the page body
    ab('eval "document.body.click()"');
    await sleep(1000);
    
    snap = ab('snapshot -i');
    const moreRef = snap.output?.match(/button "More [Ff]ilters" \[ref=(e\d+)\]/)?.[1];
    if (moreRef) {
      ab(`click @${moreRef}`);
      await sleep(3000);

      // Click Sales tab
      snap = ab('snapshot -i');
      const salesRef = snap.output?.match(/tab "Sales[^"]*" \[ref=(e\d+)\]/)?.[1];
      if (salesRef) {
        ab(`click @${salesRef}`);
        await sleep(2000);

        // Parse filter config
        const [withinType, period] = CONFIG.saleFilter.split('_within_');
        const notWithin = withinType === 'not';
        const periodMap = {
          '90d': 'Past 90 days', '1y': 'Past year', '2y': 'Past 2 years',
          '5y': 'Past 5 years', '10y': 'Past 10 years'
        };
        const periodText = periodMap[period] || 'Past 10 years';

        // Click Not Within / Within using JS
        const withinLabel = notWithin ? 'Not Within' : 'Within';
        ab(`eval "
          const divs = Array.from(document.querySelectorAll('div'));
          const btn = divs.find(d => d.textContent.trim() === '${withinLabel}' && d.className.includes('jss'));
          if (btn) btn.click();
          '${withinLabel}: ' + !!btn;
        "`);
        await sleep(1500);

        ab(`eval "
          const divs = Array.from(document.querySelectorAll('div'));
          const btn = divs.find(d => d.textContent.trim() === '${periodText}' && d.className.includes('jss'));
          if (btn) btn.click();
          '${periodText}: ' + !!btn;
        "`);
        await sleep(1500);

        // Click Apply in more filters
        snap = ab('snapshot -i');
        const salesApply = snap.output?.match(/button "Apply" \[ref=(e\d+)\]/)?.[1];
        if (salesApply) {
          ab(`click @${salesApply}`);
          await sleep(5000);
        }
      }
    }
  }

  // ── Owner filters (phone/email) ──
  if (CONFIG.ownerPhone || CONFIG.ownerEmail) {
    log(`  Setting owner filters: phone=${CONFIG.ownerPhone}, email=${CONFIG.ownerEmail}`);
    snap = ab('snapshot -i');
    let moreRef = snap.output?.match(/button "More [Ff]ilters" \[ref=(e\d+)\]/)?.[1];
    // If More Filters panel is already open, look for the close/filter button
    if (!moreRef) {
      // Panel might already be open from sales filter
      const closeRef = snap.output?.match(/button "Close" \[ref=(e\d+)\]/)?.[1];
      if (closeRef) {
        // Close and reopen
        ab(`click @${closeRef}`);
        await sleep(1000);
        snap = ab('snapshot -i');
        moreRef = snap.output?.match(/button "More [Ff]ilters" \[ref=(e\d+)\]/)?.[1];
      }
    }
    
    if (moreRef) {
      ab(`click @${moreRef}`);
      await sleep(2000);
    }

    // Click Owner tab
    snap = ab('snapshot -i');
    const ownerTabRef = snap.output?.match(/tab "Owner[^"]*" \[ref=(e\d+)\]/)?.[1];
    if (ownerTabRef) {
      ab(`click @${ownerTabRef}`);
      await sleep(2000);

      // Use JS to click phone/email toggles
      if (CONFIG.ownerPhone) {
        ab(`eval "
          const divs = Array.from(document.querySelectorAll('div'));
          const phone = divs.find(d => d.textContent.trim() === 'Includes Phone Number' && d.className.includes('jss'));
          if (phone) phone.click();
          'phone: ' + !!phone;
        "`);
        await sleep(1000);
      }
      if (CONFIG.ownerEmail) {
        ab(`eval "
          const divs = Array.from(document.querySelectorAll('div'));
          const email = divs.find(d => d.textContent.trim() === 'Includes Email Address' && d.className.includes('jss'));
          if (email) email.click();
          'email: ' + !!email;
        "`);
        await sleep(1000);
      }

      snap = ab('snapshot -i');
      const ownerApply = snap.output?.match(/button "Apply" \[ref=(e\d+)\]/)?.[1];
      if (ownerApply) {
        ab(`click @${ownerApply}`);
        await sleep(5000);
      }
    }
  }

  // ── Get search ID and property count ──
  await sleep(2000);
  const url = ab('eval "window.location.href"');
  const searchIdMatch = url.output?.match(/search\/([a-f0-9-]+)/);
  const searchId = searchIdMatch ? searchIdMatch[1] : 'unknown';

  snap = ab('snapshot');
  const countMatch = snap.output?.match(/heading "([0-9,]+) properties"/);
  const propertyCount = countMatch ? countMatch[1] : '?';

  log(`\n  Search ready: ${propertyCount} properties`);
  log(`  Search ID: ${searchId}`);
  log(`  URL: ${url.output}`);

  return searchId;
}

// ── EXTRACT OWNERS FROM TAB ──
function extractOwnersFromTab(snapshot) {
  if (!snapshot) return [];
  const owners = [];
  const lines = snapshot.split('\n');
  let currentOwner = null;

  for (const line of lines) {
    const ownerMatch = line.match(/link "([^"]+)" \[ref=e\d+\]/);
    if (ownerMatch && !ownerMatch[1].includes('Call') && !ownerMatch[1].includes('Send')
        && !ownerMatch[1].includes('Sign') && !ownerMatch[1].includes('Advanced')
        && !ownerMatch[1].includes('http') && !ownerMatch[1].includes("Don't")
        && !ownerMatch[1].includes('Google') && !ownerMatch[1].includes('Terms')
        && !ownerMatch[1].includes('Report')) {
      const name = ownerMatch[1];
      if (name.length > 2 && name.length < 80 && /[A-Z]/.test(name)) {
        if (currentOwner && (currentOwner.phones.length > 0 || currentOwner.emails.length > 0)) {
          owners.push(currentOwner);
        }
        const cleanName = name.replace(/\s+(President|CEO|Manager|Member|Director|Officer|Secretary|Treasurer|VP|Vice President|Partner|Owner|Agent|Trustee|Chairman|Principal)$/i, '').trim();
        currentOwner = { name: cleanName, phones: [], emails: [] };
      }
    }

    const phoneMatch = line.match(/button "(?:1-)?(\d{3}-\d{3}-\d{4})(?:\s+(\w+))?" \[ref=e\d+\]/);
    if (phoneMatch && currentOwner) {
      const phone = line.includes('1-') ? `1-${phoneMatch[1]}` : phoneMatch[1];
      currentOwner.phones.push({ number: phone, type: phoneMatch[2] || 'Unknown' });
    }

    const longPhoneMatch = line.match(/button "(\d{10,14})" \[ref=e\d+\]/);
    if (longPhoneMatch && currentOwner) {
      currentOwner.phones.push({ number: longPhoneMatch[1], type: 'Unknown' });
    }

    const emailMatch = line.match(/button "([a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,})" \[ref=e\d+\]/);
    if (emailMatch && currentOwner) {
      currentOwner.emails.push(emailMatch[1]);
    }
  }

  if (currentOwner && (currentOwner.phones.length > 0 || currentOwner.emails.length > 0)) {
    owners.push(currentOwner);
  }

  // Dedupe
  const deduped = [];
  const seen = new Map();
  for (const o of owners) {
    const key = o.name.toLowerCase();
    if (seen.has(key)) {
      const ex = seen.get(key);
      for (const p of o.phones) { if (!ex.phones.find(ep => ep.number === p.number)) ex.phones.push(p); }
      for (const e of o.emails) { if (!ex.emails.includes(e)) ex.emails.push(e); }
    } else {
      seen.set(key, { ...o });
      deduped.push(seen.get(key));
    }
  }
  return deduped;
}

// ── SCRAPE PROPERTIES ──
async function scrapeProperties(searchId) {
  log('\n== Scraping properties ==');
  const dailyStats = getDailyStats();
  const remainingToday = CONFIG.maxDailyProperties - dailyStats.propertiesScraped;
  const maxThisRun = Math.min(CONFIG.maxProperties, remainingToday);
  log(`  Daily: ${dailyStats.propertiesScraped} done, ${remainingToday} remaining, this run: max ${maxThisRun}`);

  if (maxThisRun <= 0) {
    log('  Daily limit reached!');
    return [];
  }

  const leads = [];
  const processed = new Set(); // Track processed addresses

  // Dismiss banners
  for (let i = 0; i < 5; i++) {
    const s = ab('snapshot -i', { verbose: false });
    const lg = s.output?.match(/button "Let's Go" \[ref=(e\d+)\]/);
    if (!lg) break;
    ab(`click @${lg[1]}`, { verbose: false });
    await sleep(800);
  }

  const parseAddresses = (snapOutput) => {
    const props = [];
    const matches = snapOutput?.matchAll(/heading "([^"]+)" \[ref=(e\d+)\] \[level=6\]/g) || [];
    for (const m of matches) {
      const text = m[1];
      if (text.includes('properties') || text.includes('Recently') ||
          text.includes('Get the most') || text.includes('What would') ||
          text.length < 10) continue;
      if (/\d+.*,\s*[A-Z]{2}\s*\d{5}/i.test(text) ||
          /\d+.*(?:st|ave|blvd|dr|ln|rd|way|ct|highway|pl|cir|route|tpke|pkwy|pike|hwy|terr?|loop|pass|trail|sq|park|grove|run|plz)/i.test(text)) {
        props.push({ name: text.substring(0, 80), ref: m[2] });
      }
    }
    return props;
  };

  // Process properties one at a time: always use CURRENT page state
  let scraped = 0;
  let scrollCount = 0;
  const maxScrolls = 10;

  while (scraped < maxThisRun && scrollCount <= maxScrolls) {
    // Take fresh snapshot of current search results
    let snap = ab('snapshot');
    const visible = parseAddresses(snap.output);

    // Find next unprocessed property
    const next = visible.find(p => !processed.has(p.name));

    if (!next) {
      // All visible properties processed, scroll for more
      scrollCount++;
      log(`  All visible processed, scrolling... (${scrollCount}/${maxScrolls})`);
      ab('scroll down 600', { verbose: false });
      await sleep(2000);
      continue;
    }

    processed.add(next.name);
    scraped++;
    log(`\n  [${scraped}/${maxThisRun}] ${next.name.substring(0, 60)}`);

    try {
      // Click the property (ref is fresh from current snapshot)
      let clickResult = ab(`click @${next.ref}`);
      if (!clickResult.success) {
        log('    Click failed, skipping');
        dailyStats.propertiesScraped++;
        continue;
      }
      await randomDelay(5000, 8000);

      // Get property URL and ID
      const propUrl = ab('eval "window.location.href"');
      const propertyId = propUrl.output?.match(/property\/([a-f0-9-]+)/)?.[1] || 'unknown';

      // Verify we're on a property page (not still on search)
      if (!propUrl.output?.includes('/property/')) {
        log('    Did not navigate to property page, skipping');
        dailyStats.propertiesScraped++;
        continue;
      }

      // Get property address from page
      snap = ab('snapshot');
      const headings = [...(snap.output?.matchAll(/heading "([^"]+)" \[ref=e\d+\]/g) || [])];
      let propertyAddress = next.name;
      for (const h of headings) {
        if (h[1].length > 5 && h[1] !== 'Owners' && h[1] !== 'Owner' && /\d/.test(h[1]) && /[a-zA-Z]/.test(h[1])) {
          propertyAddress = h[1];
          break;
        }
      }

      // Extract property info
      const iSnap = ab('snapshot -i');
      const buildText = iSnap.output || '';
      const propertyInfo = {};

      const sfMatch = buildText.match(/(\d[\d,]*(?:\.\d+)?)\s*(?:SF|Sq\.?\s*Ft)/i) || next.name.match(/([\d.]+k?)\s*SF/i);
      if (sfMatch) propertyInfo.squareFootage = sfMatch[1].replace(/,/g, '');

      const typeMatch = buildText.match(/(?:Property Type|Type)[:\s]*([A-Za-z\s()]+?)(?:\n|$)/i);
      if (typeMatch) propertyInfo.propertyType = typeMatch[1]?.trim();
      if (!propertyInfo.propertyType) {
        const typeFromName = next.name.match(/(?:Industrial|Warehouse|Manufacturing|Distribution|Flex|Storage|Factory)[^"]*/i);
        if (typeFromName) propertyInfo.propertyType = typeFromName[0].trim();
      }

      const yearMatch = buildText.match(/(?:Year Built|Built)[:\s]*(\d{4})/i);
      if (yearMatch) propertyInfo.yearBuilt = yearMatch[1];

      const lotMatch = buildText.match(/([\d.]+)\s*Acre/i);
      if (lotMatch) propertyInfo.lotSize = lotMatch[1] + ' Acres';

      const unitsMatch = buildText.match(/(\d+)\s*Unit/i) || next.name.match(/(\d+)\s*Unit/i);
      if (unitsMatch) propertyInfo.units = unitsMatch[1];

      const addrParts = propertyAddress.match(/,\s*([^,]+),\s*([A-Z]{2})\s*(\d{5})/i);
      if (addrParts) {
        propertyInfo.city = addrParts[1].trim();
        propertyInfo.state = addrParts[2];
        propertyInfo.zip = addrParts[3];
      }

      log(`    ${propertyAddress} | ${JSON.stringify(propertyInfo)}`);

      // Click Owner tab
      log('    Opening Owner tab...');
      ab('find role tab click --name "Owner"');
      await randomDelay(4000, 6000);

      // Extract contacts
      const ownerSnap = ab('snapshot -i');
      const owners = extractOwnersFromTab(ownerSnap.output || '');

      if (owners.length === 0) {
        log('    No contacts found');
      } else {
        const totalPhones = owners.reduce((s, o) => s + o.phones.length, 0);
        const totalEmails = owners.reduce((s, o) => s + o.emails.length, 0);
        log(`    ${owners.length} owners, ${totalPhones} phones, ${totalEmails} emails`);

        leads.push({
          scrapeDate: new Date().toISOString(),
          propertyId,
          propertyAddress,
          ...propertyInfo,
          owners: owners.map(o => ({ name: o.name, phones: o.phones, emails: o.emails }))
        });
        dailyStats.leadsFound++;
        log('    Lead captured!');
      }

      dailyStats.propertiesScraped++;
      saveDailyStats(dailyStats);

      // Navigate back to search
      ab(`open "https://app.reonomy.com/#!/search/${searchId}"`);
      await randomDelay(5000, 8000);

      // Random longer break
      if (Math.random() < 0.15) {
        log('    Taking a short break...');
        await randomDelay(8000, 15000);
      }

    } catch (propError) {
      log(`    Error: ${propError.message}`);
      ab(`open "https://app.reonomy.com/#!/search/${searchId}"`);
      await sleep(6000);
      dailyStats.propertiesScraped++;
      saveDailyStats(dailyStats);
    }
  }

  return leads;
}

// ── MAIN ──
async function main() {
  // Clear log
  fs.writeFileSync(CONFIG.logPath, '');
  log('=== Reonomy Scraper v14 ===');
  log(`Filters: state=${CONFIG.searchState}, types=${CONFIG.propertyTypes}, minSF=${CONFIG.minSF}, sale=${CONFIG.saleFilter}`);
  log(`Owner filters: phone=${CONFIG.ownerPhone}, email=${CONFIG.ownerEmail}`);
  log(`Max properties: ${CONFIG.maxProperties}`);

  try {
    // Login
    log('\n== Step 1: Login ==');
    await login();

    // Build search
    const searchId = await buildSearch();

    // Scrape
    const leads = await scrapeProperties(searchId);

    // Save results
    log('\n== Saving results ==');
    let allLeads = [];
    try {
      const existing = JSON.parse(fs.readFileSync(CONFIG.outputPath, 'utf8'));
      allLeads = existing.leads || [];
    } catch (e) {}

    allLeads = [...allLeads, ...leads];

    const output = {
      lastUpdated: new Date().toISOString(),
      searchId,
      filters: {
        state: CONFIG.searchState,
        propertyTypes: CONFIG.propertyTypes,
        minSF: CONFIG.minSF,
        saleFilter: CONFIG.saleFilter,
        ownerPhone: CONFIG.ownerPhone,
        ownerEmail: CONFIG.ownerEmail,
      },
      totalLeads: allLeads.length,
      leads: allLeads
    };

    fs.writeFileSync(CONFIG.outputPath, JSON.stringify(output, null, 2));
    log(`Saved ${leads.length} new leads (${allLeads.length} total)`);

    return leads;

  } catch (error) {
    log(`\nFATAL: ${error.message}`);
    ab('screenshot /tmp/reonomy-v14-error.png');
    throw error;
  } finally {
    log('\nClosing browser...');
    ab('close');
  }
}

main()
  .then(leads => {
    log(`\nDone! ${leads.length} leads scraped.`);
    process.exit(0);
  })
  .catch(err => {
    log(`\nFailed: ${err.message}`);
    process.exit(1);
  });
