const { useState: useStateA, useEffect: useEffectA, useMemo: useMemoA } = React;

function useIsMobile(bp) {
  const q = `(max-width:${bp || 760}px)`;
  const read = () => typeof window !== 'undefined' && window.matchMedia(q).matches;
  const [m, setM] = useStateA(read);
  useEffectA(() => {
    const mq = window.matchMedia(q);
    const onChange = () => setM(mq.matches);
    onChange();
    mq.addEventListener ? mq.addEventListener('change', onChange) : mq.addListener(onChange);
    return () => { mq.removeEventListener ? mq.removeEventListener('change', onChange) : mq.removeListener(onChange); };
  }, []);
  return m;
}

function upgradeEbayImageUrl(url) {
  if (!url) return url;
  return url.replace(/\/s-l\d+\./, '/s-l960.');
}

function getAspect(aspects, name) {
  if (!aspects) return null;
  const match = aspects.find(a => a.name.toLowerCase() === name.toLowerCase());
  return match ? match.value : null;
}

function parseShippingEta(shippingOptions) {
  if (!shippingOptions || shippingOptions.length === 0) return 'Check listing';
  const opt = shippingOptions[0];
  if (opt.maxEstimatedDeliveryDate) {
    const max = new Date(opt.maxEstimatedDeliveryDate);
    const now = new Date();
    const days = Math.max(1, Math.ceil((max - now) / (1000 * 60 * 60 * 24)));
    if (days <= 1) return '1 day';
    if (days <= 3) return '1–3 days';
    if (days <= 5) return '3–5 days';
    return `${days} days`;
  }
  return 'Check listing';
}

function enrichApiItems(apiItems) {
  return apiItems.map((item, i) => {
    const price = parseFloat(item.price.value);
    const isUsed = item.condition ? item.condition.includes('Used') : false;
    const isNew = item.condition ? item.condition.includes('New') : false;
    const images = [];
    if (item.image?.imageUrl) images.push(upgradeEbayImageUrl(item.image.imageUrl));
    if (item.additionalImages) {
      item.additionalImages.forEach((img) => { if (img.imageUrl) images.push(upgradeEbayImageUrl(img.imageUrl)); });
    }

    const aspects = item.localizedAspects || [];
    const warranty = getAspect(aspects, 'Manufacturer Warranty')
      || getAspect(aspects, 'Warranty')
      || getAspect(aspects, 'Custom Bundle')
      || (isNew ? 'Check listing' : 'Check listing');
    const brand = getAspect(aspects, 'Brand') || getAspect(aspects, 'Manufacturer');
    const mpn = getAspect(aspects, 'Manufacturer Part Number') || getAspect(aspects, 'MPN');
    const oem = isUsed || (brand ? /genuine|oem|original/i.test(brand) : false);

    const shippingCost = item.shippingOptions?.[0]?.shippingCost
      ? parseFloat(item.shippingOptions[0].shippingCost.value)
      : 0;

    const sellerInfo = item.sellerDetail || item.seller || {};
    const sellerName = sellerInfo.username || 'eBay Seller';
    const feedbackPct = sellerInfo.feedbackPercentage
      ? parseFloat(sellerInfo.feedbackPercentage)
      : 0;
    const feedbackScore = sellerInfo.feedbackScore || 0;

    const loc = item.itemLocation || {};
    const locationCity = loc.city || '';
    const locationState = loc.stateOrProvince || '';
    const locationCountry = loc.country || '';
    const locationParts = [locationCity, locationState, locationCountry].filter(Boolean);
    const location = locationParts.length > 0 ? locationParts.join(', ') : 'UK';

    return {
      id: item.itemId || `item-${i}`,
      title: item.title,
      price: price,
      condition: isNew ? 'New' : isUsed ? 'Used' : 'Unknown',
      itemWebUrl: item.itemAffiliateWebUrl || item.itemWebUrl,
      grade: isNew ? 'New' : 'Used',
      seller: sellerName,
      feedback: feedbackPct,
      sales: feedbackScore,
      location: location,
      postage: shippingCost,
      eta: parseShippingEta(item.shippingOptions),
      warranty: warranty,
      mileage: getAspect(aspects, 'Mileage'),
      oem: oem,
      sku: mpn || 'N/A',
      brand: brand || '',
      fitConfidence: null,
      fitTier: null,
      fitReason: null,
      photos: images.length || 1,
      imageUrls: images,
      aspects: aspects,
      sellerDetail: {
        username: sellerName,
        feedbackPercentage: feedbackPct,
        feedbackScore: feedbackScore,
        location: location,
      },
      description: item.description || '',
      returnTerms: item.returnTerms || null,
      compatibleVehicles: item.compatibleVehicles || [],
    };
  });
}

/* ---------- Fitment scoring ----------
 * Returns a tier ('confirmed' | 'likely' | 'possible' | 'unknown' | 'mismatch'),
 * a derived 0–100 score for sorting/display, and a short human reason.
 * Tiers are the source of truth; the score is just a presentation layer.
 */
const FIT_MAKE_ALIASES = {
  'VW': 'VOLKSWAGEN', 'VOLKSWAGEN': 'VOLKSWAGEN',
  'MERCEDES': 'MERCEDES', 'MERCEDES-BENZ': 'MERCEDES', 'MERC': 'MERCEDES',
  'LAND ROVER': 'LAND ROVER', 'LANDROVER': 'LAND ROVER', 'RANGE ROVER': 'LAND ROVER',
};
function normMake(m) {
  const u = (m || '').toUpperCase().trim();
  return FIT_MAKE_ALIASES[u] || u;
}
function firstWord(s) { return (s || '').toUpperCase().trim().split(/\s+/)[0] || ''; }

const FIT_KNOWN_MAKES = [
  'FORD','VAUXHALL','BMW','AUDI','VW','VOLKSWAGEN','MERCEDES','MERCEDES-BENZ',
  'TOYOTA','NISSAN','HONDA','PEUGEOT','RENAULT','SKODA','SEAT','KIA','HYUNDAI',
  'VOLVO','MAZDA','MINI','FIAT','CITROEN','JAGUAR','LAND ROVER','PORSCHE','TESLA',
  'SUZUKI','SUBARU','LEXUS','DACIA','ALFA','SAAB','SMART','MITSUBISHI','CHEVROLET',
];

function parseYearRangeFromTitle(title) {
  const range = title.match(/\b(20\d{2}|19\d{2})\s*[-–—to]+\s*(20\d{2}|19\d{2}|\d{2})\b/i);
  if (range) {
    const s = parseInt(range[1], 10);
    let e = parseInt(range[2], 10);
    if (e < 100) e += Math.floor(s / 100) * 100;
    return { start: s, end: e, label: `${s}–${e}` };
  }
  const single = title.match(/\b(20\d{2}|19\d{2})\b/);
  if (single) {
    const y = parseInt(single[1], 10);
    return { start: y, end: y, label: String(y) };
  }
  return null;
}

/* Tier "bands" — modifiers nudge the score within these. Confirmed never
 * spills into mismatch territory and vice versa. */
const FIT_TIER_BAND = {
  confirmed: { min: 91, max: 99 },
  likely:    { min: 78, max: 90 },
  possible:  { min: 66, max: 78 },
  unknown:   { min: 58, max: 70 },
  mismatch:  { min: 18, max: 42 },
};
function clampToBand(tier, score) {
  const b = FIT_TIER_BAND[tier] || FIT_TIER_BAND.unknown;
  return Math.max(b.min, Math.min(b.max, score));
}

/* Extra evidence either side of the tier decision. Each modifier nudges the
 * score and optionally adds a short tag to the reason. Keeps tiers stable
 * but lets the number reflect how strong the evidence is. */
function fitModifiers(part, vehicle, ctx) {
  const title = (part.title || '').toLowerCase();
  const aspects = part.localizedAspects || part.aspects || [];
  const aspectBlob = aspects.map(a => `${a.name}: ${a.value}`).join(' ').toLowerCase();
  const haystack = `${title} ${aspectBlob}`;
  let delta = 0;
  const tags = [];

  // Trim / variant match (e.g. "Ultimate", "Sport", "GT Line")
  const trim = (vehicle.Trim || '').trim();
  if (trim) {
    const trimWords = trim.toLowerCase().split(/\s+/).filter(w => w.length >= 3);
    const trimHit = trimWords.find(w => new RegExp(`\\b${w}\\b`, 'i').test(haystack));
    if (trimHit) { delta += 2; tags.push(`${trim} trim`); }
  }

  // Fuel type — only meaningful for parts where it matters (engine/exhaust/etc.)
  const fuel = (vehicle.FuelType || '').toLowerCase();
  if (fuel) {
    const fuelTerms = {
      petrol: ['petrol', 'gasoline'],
      diesel: ['diesel', 'tdi', 'cdti', 'hdi', 'dci'],
      electric: ['electric', 'ev', 'bev'],
      hybrid: ['hybrid', 'phev'],
    };
    const matchKey = Object.keys(fuelTerms).find(k => fuel.includes(k));
    if (matchKey) {
      const hit = fuelTerms[matchKey].some(t => new RegExp(`\\b${t}\\b`, 'i').test(haystack));
      const other = Object.entries(fuelTerms).find(([k, terms]) =>
        k !== matchKey && terms.some(t => new RegExp(`\\b${t}\\b`, 'i').test(haystack)));
      if (hit) { delta += 1; tags.push(`${matchKey} match`); }
      else if (other) { delta -= 2; tags.push(`${other[0]} listing`); }
    }
  }

  // Transmission
  const trans = (vehicle.Transmission || '').toLowerCase();
  if (trans) {
    const wantsManual = /manual/.test(trans);
    const wantsAuto = /auto|cvt|dsg|dct/.test(trans);
    const titleManual = /\bmanual\b/.test(haystack);
    const titleAuto = /\b(auto|automatic|cvt|dsg|dct)\b/.test(haystack);
    if (wantsManual && titleManual) { delta += 1; tags.push('manual'); }
    else if (wantsAuto && titleAuto) { delta += 1; tags.push('auto'); }
    else if (wantsManual && titleAuto) { delta -= 1; }
    else if (wantsAuto && titleManual) { delta -= 1; }
  }

  // Engine size — useful for engine-bay parts (alternator, starter, etc.)
  const cc = (vehicle.EngineCapacity || '').toLowerCase();
  if (cc) {
    const ccMatch = cc.match(/(\d+(?:\.\d+)?)l/);
    if (ccMatch && new RegExp(`\\b${ccMatch[1].replace('.', '\\.')}\\s*l\\b`, 'i').test(haystack)) {
      delta += 1; tags.push(`${ccMatch[1]}L`);
    }
  }

  // Multiple structured compat rows confirming the same vehicle = stronger
  if (ctx.confirmedCvCount >= 2) { delta += 1; }

  // Title contains other years alongside ours (vague listings)
  const allYears = (part.title || '').match(/\b(20\d{2}|19\d{2})\b/g) || [];
  const vYear = parseInt(vehicle.YearOfManufacture, 10);
  if (!isNaN(vYear) && allYears.length > 0) {
    const hasOurs = allYears.includes(String(vYear));
    const hasOthers = allYears.some(y => parseInt(y, 10) !== vYear);
    if (hasOthers && !hasOurs && !ctx.yearRangeCovers) { delta -= 2; }
  }

  return { delta, tags };
}

function scoreFitment(part, vehicle) {
  if (!vehicle) return { tier: 'unknown', score: 65, reason: 'No vehicle selected' };

  const vMake = normMake(vehicle.Make);
  const vModel = firstWord(vehicle.Model);
  const vYear = parseInt(vehicle.YearOfManufacture, 10);
  const vehicleLabel = `${vehicle.Make} ${vehicle.Model}`.trim();

  const cvs = part.compatibleVehicles || [];

  // Tier decision (unchanged) ----------------------------------------------
  let cvMakeModelHit = false;
  let cvExactMatch = null;
  let confirmedCvCount = 0;
  for (const cv of cvs) {
    const cvMake = normMake(cv.Make || cv.Car_Make);
    const cvModel = firstWord(cv.Model || cv.Car_Model);
    const cvYear = parseInt(cv.Year || cv.Car_Year, 10);
    if (cvMake && cvMake === vMake && cvModel && cvModel === vModel) {
      cvMakeModelHit = true;
      if (!isNaN(vYear) && !isNaN(cvYear) && cvYear === vYear) {
        confirmedCvCount += 1;
        cvExactMatch = cvExactMatch || { year: cvYear };
      }
    }
  }

  const title = (part.title || '').toUpperCase();
  const titleMakes = new Set();
  for (const m of FIT_KNOWN_MAKES) {
    if (new RegExp(`\\b${m.replace(/[- ]/g, '[- ]?')}\\b`, 'i').test(title)) {
      titleMakes.add(normMake(m));
    }
  }
  const titleHasOurMake = titleMakes.has(vMake);
  const titleHasOurModel = vModel && new RegExp(`\\b${vModel}\\b`, 'i').test(title);
  const yr = parseYearRangeFromTitle(part.title || '');
  const yearRangeCovers = yr && !isNaN(vYear) && vYear >= yr.start && vYear <= yr.end;

  let tier, baseScore, reason;
  if (cvExactMatch) {
    tier = 'confirmed'; baseScore = 96;
    reason = `Seller lists this for ${vehicleLabel} (${cvExactMatch.year})`;
  } else if (cvMakeModelHit) {
    tier = 'confirmed'; baseScore = 92;
    reason = `Listed for ${vehicleLabel} (year not specified in seller data)`;
  } else if (titleHasOurMake && titleHasOurModel && yr && !isNaN(vYear) && !yearRangeCovers) {
    tier = 'mismatch'; baseScore = 32;
    reason = `Listing is for ${yr.label} — your vehicle is ${vYear}`;
  } else if (titleHasOurMake && titleHasOurModel && yearRangeCovers) {
    tier = 'likely'; baseScore = 88;
    reason = `Title matches your ${vehicleLabel} for ${yr.label}`;
  } else if (titleHasOurMake && titleHasOurModel) {
    tier = 'likely'; baseScore = 82;
    reason = `Title mentions ${vehicleLabel} — year not stated`;
  } else if (cvs.length > 0) {
    const others = [...new Set(cvs.slice(0, 3).map(cv =>
      `${cv.Make || ''} ${cv.Model || ''}`.trim()).filter(Boolean))].join(', ');
    tier = 'mismatch'; baseScore = 28;
    reason = others ? `Seller lists this for: ${others} — not your vehicle`
                    : `Seller's compatibility list doesn't include your vehicle`;
  } else {
    const otherMakes = [...titleMakes].filter(m => m !== vMake);
    if (otherMakes.length > 0) {
      tier = 'mismatch'; baseScore = 30;
      reason = `Title mentions ${otherMakes[0]} — not your ${vehicle.Make}`;
    } else if (titleHasOurMake) {
      tier = 'possible'; baseScore = 72;
      reason = `Listed for ${vehicle.Make} — model not specified`;
    } else {
      tier = 'unknown'; baseScore = 65;
      reason = `No fitment data — check the seller's listing before buying`;
    }
  }

  // Modifier pass -----------------------------------------------------------
  const { delta, tags } = fitModifiers(part, vehicle, { confirmedCvCount, yearRangeCovers });
  const score = clampToBand(tier, baseScore + delta);
  const finalReason = tags.length > 0 ? `${reason} · ${tags.join(', ')}` : reason;
  return { tier, score, reason: finalReason };
}

function FindMyPartApp() {
  const [reg, setReg] = useStateA('');
  const [part, setPart] = useStateA('');
  const [searchedPart, setSearchedPart] = useStateA('');
  const [stage, setStage] = useStateA('landing');
  const [loadStep, setLoadStep] = useStateA(0);
  const [tab, setTab] = useStateA('all');
  const [sort, setSort] = useStateA('best-value');
  const [compared, setCompared] = useStateA([]);
  const [selected, setSelected] = useStateA(null);
  const [detailPart, setDetailPart] = useStateA(null);
  const [vehicle, setVehicle] = useStateA(null);
  const [rawPool, setRawPool] = useStateA([]);
  const [reportedIds, setReportedIds] = useStateA([]);
  const [toast, setToast] = useStateA(null);
  const [enrichmentById, setEnrichmentById] = useStateA({});
  const [page, setPage] = useStateA(1);
  const [enrichingPage, setEnrichingPage] = useStateA(false);
  const PAGE_SIZE = 12;
  const [searchQuery, setSearchQuery] = useStateA('');
  const [apiError, setApiError] = useStateA(null);
  const [cheapestUsedApi, setCheapestUsedApi] = useStateA(null);
  const [cheapestNewApi, setCheapestNewApi] = useStateA(null);
  const [filters, setFilters] = useStateA({
    condition: { used: true, 'new-oem': true, 'new-after': true },
    priceMin: '', priceMax: '',
    fit: 'any',
    freePostage: false, fastEta: false,
    topSeller: false, ukOnly: true,
  });

  const search = async () => {
    if (!reg.trim() || !part.trim()) return;
    setStage('loading');
    setLoadStep(0);
    setApiError(null);
    setCompared([]);
    setSelected(null);
    setTab('all');

    setTimeout(() => setLoadStep(1), 400);

    try {
      const vrmClean = reg.replace(/\s/g, '');
      window.fmpTrack && window.fmpTrack('search_submitted', { part: part.trim(), vrm_length: vrmClean.length });
      const res = await fetch(`/api/search?vrm=${encodeURIComponent(vrmClean)}&part=${encodeURIComponent(part)}`);

      if (!res.ok) {
        const err = await res.json();
        setApiError(err.error || 'Search failed');
        setStage('landing');
        return;
      }

      const data = await res.json();

      setVehicle(data.vehicle);
      setLoadStep(2);
      setSearchQuery(data.searchQuery);

      await new Promise(r => setTimeout(r, 600));
      setLoadStep(3);

      if (data.analysis.cheapestUsed !== null) setCheapestUsedApi(data.analysis.cheapestUsed);
      if (data.analysis.cheapestNew !== null) setCheapestNewApi(data.analysis.cheapestNew);

      setRawPool(data.items || []);
      setEnrichmentById({});
      setReportedIds([]);
      setPage(1);

      const tierCounts = { confirmed: 0, likely: 0, possible: 0, unknown: 0, mismatch: 0 };
      (data.items || []).forEach(it => {
        const f = scoreFitment({ title: it.title, compatibleVehicles: it.compatibleVehicles || [] }, data.vehicle);
        tierCounts[f.tier] = (tierCounts[f.tier] || 0) + 1;
      });
      window.fmpTrack && window.fmpTrack('search_succeeded', {
        part: part.trim(),
        vehicle_make: data.vehicle && data.vehicle.make,
        vehicle_model: data.vehicle && data.vehicle.model,
        results_count: (data.items || []).length,
        cheapest_used: data.analysis && data.analysis.cheapestUsed,
        cheapest_new: data.analysis && data.analysis.cheapestNew,
        fit_confirmed: tierCounts.confirmed,
        fit_likely: tierCounts.likely,
        fit_possible: tierCounts.possible,
        fit_unknown: tierCounts.unknown,
        fit_mismatch: tierCounts.mismatch,
      });

      setSearchedPart(part.trim());
      await new Promise(r => setTimeout(r, 500));
      setStage('results');
    } catch (err) {
      setApiError(err.message);
      window.fmpTrack && window.fmpTrack('search_failed', { part: part.trim(), error: String(err && err.message) });
      setStage('landing');
    }
  };

  const goLanding = () => {
    setStage('landing');
    setCompared([]);
    setSelected(null);
    setDetailPart(null);
  };

  const viewDetail = (partItem) => {
    setDetailPart(partItem);
    setStage('detail');
    window.scrollTo({ top: 0, behavior: 'smooth' });
  };

  const backToResults = () => {
    setDetailPart(null);
    setStage('results');
  };

  // Merge lightweight pool items with any enrichment we've fetched, then
  // run the existing transformer. Un-enriched items render with sensible
  // defaults; once enrichment lands for a page, we just re-derive.
  const parts = useMemoA(() => {
    const merged = rawPool.filter(raw => !reportedIds.includes(raw.itemId)).map(raw => {
      const e = enrichmentById[raw.itemId];
      if (!e) return raw;
      return {
        ...raw,
        localizedAspects: e.localizedAspects,
        sellerDetail: e.seller || undefined,
        description: e.description,
        itemLocation: e.itemLocation,
        returnTerms: e.returnTerms,
        compatibleVehicles: e.compatibleVehicles,
      };
    });
    const enriched = enrichApiItems(merged);
    return enriched.map(p => {
      const fit = scoreFitment(p, vehicle);
      return { ...p, fitConfidence: fit.score, fitTier: fit.tier, fitReason: fit.reason };
    });
  }, [rawPool, enrichmentById, reportedIds, vehicle]);

  const reportListing = (p) => {
    if (!p) return;
    window.fmpTrack && window.fmpTrack('listing_reported', {
      item_id: p.id,
      title: p.title,
      condition: p.condition,
      oem: p.oem,
      price: p.price,
      seller: p.seller,
      item_url: p.itemWebUrl,
      part: part.trim(),
      search_query: searchQuery,
      vehicle_make: vehicle && vehicle.Make,
      vehicle_model: vehicle && vehicle.Model,
    });
    setReportedIds(xs => xs.includes(p.id) ? xs : [...xs, p.id]);
    setCompared(xs => xs.filter(x => x !== p.id));
    if (selected === p.id) setSelected(null);
    setToast('Listing reported. Thanks — it’s been removed from your results.');
    if (stage === 'detail' && detailPart && detailPart.id === p.id) {
      setDetailPart(null);
      setStage('results');
    }
  };

  const filtered = useMemoA(() => {
    let xs = parts.slice();
    if (tab === 'used') xs = xs.filter(p => p.condition === 'Used');
    if (tab === 'new') xs = xs.filter(p => p.condition === 'New');
    xs = xs.filter(p => {
      if (p.condition === 'Used') return filters.condition.used;
      if (p.condition === 'New' && p.oem) return filters.condition['new-oem'];
      if (p.condition === 'New' && !p.oem) return filters.condition['new-after'];
      return true;
    });
    if (filters.priceMin) xs = xs.filter(p => p.price >= parseFloat(filters.priceMin));
    if (filters.priceMax) xs = xs.filter(p => p.price <= parseFloat(filters.priceMax));
    if (filters.freePostage) xs = xs.filter(p => p.postage === 0);
    if (filters.fastEta) xs = xs.filter(p => /[12]/.test(p.eta));
    if (filters.topSeller) xs = xs.filter(p => p.feedback >= 99);
    if (filters.fit === 'exact') xs = xs.filter(p => p.fitTier === 'confirmed');
    else if (filters.fit === 'likely') xs = xs.filter(p => p.fitTier === 'confirmed' || p.fitTier === 'likely');
    else xs = xs.filter(p => p.fitTier !== 'mismatch');
    const sorters = {
      'best-value': (a, b) => (a.price + a.postage) - (b.price + b.postage),
      'price-asc': (a, b) => a.price - b.price,
      'price-desc': (a, b) => b.price - a.price,
      'fit-desc': (a, b) => b.fitConfidence - a.fitConfidence,
    };
    xs.sort(sorters[sort]);
    return xs;
  }, [parts, tab, sort, filters]);

  const counts = useMemoA(() => ({
    all: parts.length,
    used: parts.filter(p => p.condition === 'Used').length,
    new: parts.filter(p => p.condition === 'New').length,
  }), [parts]);

  const cheapestUsed = useMemoA(() => {
    if (cheapestUsedApi !== null) return cheapestUsedApi;
    const used = parts.filter(p => p.condition === 'Used');
    return used.length > 0 ? Math.min(...used.map(p => p.price)) : 0;
  }, [parts, cheapestUsedApi]);

  const cheapestNew = useMemoA(() => {
    if (cheapestNewApi !== null) return cheapestNewApi;
    const n = parts.filter(p => p.condition === 'New');
    return n.length > 0 ? Math.min(...n.map(p => p.price)) : 0;
  }, [parts, cheapestNewApi]);

  const bestId = useMemoA(() => {
    const c = [...parts].sort((a, b) => (a.price + a.postage) - (b.price + b.postage))[0];
    return c?.id;
  }, [parts]);

  const totalPages = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE));
  const safePage = Math.min(page, totalPages);
  const pageStart = (safePage - 1) * PAGE_SIZE;
  const pageItems = filtered.slice(pageStart, pageStart + PAGE_SIZE);

  // Whenever the visible page changes, request enrichment for any items on
  // that page we haven't enriched yet. This keeps the per-search API cost
  // proportional to pages the user actually views.
  useEffectA(() => {
    const need = pageItems.map(p => p.id).filter(id => !enrichmentById[id]);
    if (need.length === 0) return;
    let cancelled = false;
    setEnrichingPage(true);
    fetch('/api/enrich', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ itemIds: need }),
    })
      .then(r => r.ok ? r.json() : null)
      .then(data => {
        if (cancelled || !data?.details) return;
        setEnrichmentById(prev => ({ ...prev, ...data.details }));
      })
      .catch(() => {})
      .finally(() => { if (!cancelled) setEnrichingPage(false); });
    return () => { cancelled = true; };
  }, [safePage, filtered.length]);

  // Reset to page 1 whenever the filtered set changes shape (filters/sort/tab).
  useEffectA(() => { setPage(1); }, [tab, sort, filters]);

  const toggleCompare = (id) => {
    setCompared(xs => xs.includes(id) ? xs.filter(x => x !== id) : [...xs, id].slice(-4));
  };

  const PageBg = (stage === 'results' || stage === 'detail') ? 'var(--surface)' : '#fff';

  return (
    <div style={{ minHeight: '100vh', background: PageBg }}>
      <TopNav dense={stage !== 'landing'} onLogo={goLanding} />

      {stage === 'landing' && (
        <Landing reg={reg} setReg={setReg} part={part} setPart={setPart} onSearch={search} error={apiError} />
      )}
      {stage === 'loading' && <LoadingView reg={reg} part={part} step={loadStep} />}
      {stage === 'results' && vehicle && (
        <ResultsView
          reg={reg} setReg={setReg} part={part} setPart={setPart}
          displayPart={searchedPart || part}
          onSearch={search}
          vehicle={vehicle} searchQuery={searchQuery}
          cheapestUsed={cheapestUsed} cheapestNew={cheapestNew}
          tab={tab} setTab={setTab} counts={counts}
          sort={sort} setSort={setSort}
          filtered={filtered}
          pageItems={pageItems}
          page={safePage} totalPages={totalPages} setPage={setPage}
          enrichingPage={enrichingPage}
          bestId={bestId}
          compared={compared}
          comparedItems={parts.filter(p => compared.includes(p.id))}
          toggleCompare={toggleCompare}
          selected={selected} setSelected={setSelected}
          filters={filters} setFilters={setFilters}
          onClearCompare={() => setCompared([])}
          onViewDetail={viewDetail}
          onReport={reportListing}
        />
      )}
      {stage === 'detail' && detailPart && vehicle && (
        <ListingDetailView
          part={detailPart}
          vehicle={vehicle}
          partName={searchedPart || part}
          onBack={backToResults}
          onReport={reportListing}
        />
      )}

      <Footer />
      <ReportToast message={toast} onDismiss={() => setToast(null)} />
    </div>
  );
}

/* ---------- Landing ---------- */
function Landing({ reg, setReg, part, setPart, onSearch, error }) {
  return (
    <main style={{ paddingBottom: 60 }}>
      <section className="fmp-hero" style={{
        background: 'linear-gradient(180deg, #f6f7f9 0%, #fff 100%)',
        padding: '56px 28px 28px',
      }}>
        <div style={{ maxWidth: 1100, margin: '0 auto' }}>
          <div style={{
            display: 'inline-flex', alignItems: 'center', gap: 8,
            background: '#fff', border: '1px solid var(--border)',
            borderRadius: 999, padding: '5px 14px 5px 6px',
            fontSize: 12, color: 'var(--ink-2)', fontWeight: 600, marginBottom: 18,
            boxShadow: 'var(--shadow-card)',
          }}>
            <span style={{
              background: 'var(--accent-soft)', color: 'var(--accent-ink)',
              padding: '2px 8px', borderRadius: 999, fontWeight: 700, fontSize: 11,
            }}>NEW</span>
            Smart Value Insight — we tell you whether used OEM beats new
          </div>
          <h1 className="fmp-hero-h1" style={{
            fontSize: 56, fontWeight: 800, color: 'var(--ink)',
            margin: '0 0 14px', lineHeight: 1.02, letterSpacing: -1.6,
            maxWidth: 920, textWrap: 'balance',
          }}>
            Find the right part for your car —<br />
            <span style={{ color: 'var(--accent)' }}>at the right price.</span>
          </h1>
          <p className="fmp-hero-p" style={{
            fontSize: 18, color: 'var(--ink-2)', margin: '0 0 28px',
            maxWidth: 620, lineHeight: 1.5,
          }}>
            Enter your UK reg and we'll find every fitting part across the UK's breakers,
            sellers and aftermarket warehouses — and tell you which one to buy.
          </p>

          <SearchCard reg={reg} setReg={setReg} part={part} setPart={setPart} onSearch={onSearch} />

          {error && (
            <div style={{
              marginTop: 14, padding: '10px 16px', background: '#fef2f2',
              border: '1px solid #fecaca', borderRadius: 8,
              color: '#991b1b', fontSize: 13, fontWeight: 600,
            }}>
              ⚠ {error}
            </div>
          )}

          <div style={{
            marginTop: 16, fontSize: 13, color: 'var(--ink-3)',
            display: 'flex', alignItems: 'center', gap: 18, flexWrap: 'wrap',
          }}>
            <span>Popular:</span>
            {['Alternator', 'Starter Motor', 'Wing Mirror', 'Headlight', 'Catalytic Converter'].map(p => (
              <button key={p} onClick={() => setPart(p)} style={{
                background: 'transparent', border: '1px solid var(--border)',
                color: 'var(--ink-2)', padding: '5px 11px', borderRadius: 999,
                fontSize: 12.5, fontWeight: 600, cursor: 'pointer',
              }}>{p}</button>
            ))}
          </div>
        </div>
      </section>

      <section style={{ maxWidth: 1100, margin: '32px auto 0', padding: '0 28px' }}>
        <TrustStrip />
      </section>

      <section style={{ maxWidth: 1100, margin: '36px auto 0', padding: '0 28px' }}>
        <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 14 }}>
          <h2 style={{ fontSize: 22, fontWeight: 800, color: 'var(--ink)', margin: 0, letterSpacing: -0.4 }}>
            How it works
          </h2>
          <a href="#" style={{ fontSize: 13, color: 'var(--accent-ink)', fontWeight: 700, textDecoration: 'none' }}>
            Learn more →
          </a>
        </div>
        <HowItWorks />
      </section>
    </main>
  );
}

/* ---------- Loading view ---------- */
function LoadingView({ reg, part, step }) {
  return (
    <main style={{ padding: '40px 28px' }}>
      <div style={{ maxWidth: 720, margin: '0 auto' }}>
        <LoadingPanel reg={reg} part={part} step={step} />
      </div>
    </main>
  );
}

/* ---------- Results view ---------- */
function pluralisePart(name) {
  const trimmed = (name || '').trim();
  if (!trimmed) return '';
  // If the last whitespace-separated word already ends in 's', leave it alone.
  return /s\s*$/i.test(trimmed) ? trimmed : trimmed + 's';
}

function ResultsView(props) {
  const {
    reg, setReg, part, setPart, displayPart, onSearch, vehicle, searchQuery,
    cheapestUsed, cheapestNew,
    tab, setTab, counts, sort, setSort,
    filtered, pageItems, page, totalPages, setPage, enrichingPage,
    bestId, compared, comparedItems, toggleCompare, selected, setSelected,
    filters, setFilters, onClearCompare, onViewDetail, onReport,
  } = props;
  const shownPart = displayPart || part;
  const isMobile = useIsMobile();
  const [filtersOpen, setFiltersOpen] = useStateA(false);

  const hasBothPrices = cheapestUsed > 0 && cheapestNew > 0;

  return (
    <main>
      <div className="fmp-results-searchbar" style={{
        background: '#fff', borderBottom: '1px solid var(--border)',
        padding: '12px 28px', position: 'sticky', top: 64, zIndex: 20,
      }}>
        <div style={{ maxWidth: 1280, margin: '0 auto' }}>
          <SearchCard compact reg={reg} setReg={setReg} part={part} setPart={setPart} onSearch={onSearch} />
        </div>
      </div>

      <div className="fmp-results-container" style={{ maxWidth: 1280, margin: '0 auto', padding: '20px 28px 90px' }}>
        <div className="fmp-fade" style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
          <VehicleMatchBanner v={vehicle} onChange={() => window.scrollTo({ top: 0, behavior: 'smooth' })} />

          {searchQuery && (
            <div style={{
              background: 'var(--blue-soft)', border: '1px solid #cfe0fb', borderRadius: 10,
              padding: '10px 16px', display: 'flex', alignItems: 'center', gap: 10,
              fontSize: 13, color: '#1448a8',
            }}>
              <span style={{ fontWeight: 700 }}>🔍 Search query:</span>
              <span className="mono" style={{ fontWeight: 600, fontSize: 13 }}>"{searchQuery}"</span>
            </div>
          )}

          {hasBothPrices && (
            <SmartValueCard cheapestUsed={cheapestUsed} cheapestNew={cheapestNew} partName={shownPart} />
          )}
        </div>

        <div className="fmp-results-grid" style={{
          marginTop: 24, display: 'grid',
          gridTemplateColumns: '260px 1fr', gap: 22, alignItems: 'flex-start',
        }}>
          {isMobile ? (
            <div>
              <button
                onClick={() => setFiltersOpen(o => !o)}
                style={{
                  width: '100%', background: '#fff', border: '1px solid var(--border-strong)',
                  borderRadius: 8, padding: '11px 12px', fontSize: 13, fontWeight: 700,
                  color: 'var(--ink-2)', cursor: 'pointer', display: 'inline-flex',
                  alignItems: 'center', justifyContent: 'center', gap: 8,
                  marginBottom: filtersOpen ? 12 : 0,
                }}>
                <Icon.Sort /> {filtersOpen ? 'Hide filters' : 'Filters'}
                <Icon.Chev style={{ transform: filtersOpen ? 'rotate(180deg)' : 'none', transition: 'transform .2s' }} />
              </button>
              {filtersOpen && <SideFilters filters={filters} setFilters={setFilters} />}
            </div>
          ) : (
            <SideFilters filters={filters} setFilters={setFilters} />
          )}

          <section>
            <div style={{
              display: 'flex', alignItems: 'center', justifyContent: 'space-between',
              marginBottom: 14, gap: 12, flexWrap: 'wrap',
            }}>
              <div>
                <div style={{ fontSize: 20, fontWeight: 800, color: 'var(--ink)', letterSpacing: -0.3 }}>
                  {filtered.length} {pluralisePart(shownPart).toLowerCase()} found for your {vehicle.Make.toLowerCase()} {vehicle.Model.toLowerCase()}
                </div>
                <div style={{ fontSize: 13, color: 'var(--ink-3)', marginTop: 2 }}>
                  Sorted by best value · live results from eBay UK
                </div>
              </div>
              <SortDropdown sort={sort} setSort={setSort} />
            </div>

            <RetailerRail vehicle={vehicle} part={shownPart} />

            <div style={{ marginBottom: 14 }}>
              <TabBar tab={tab} setTab={setTab} counts={counts} />
            </div>

            <div style={{ display: 'flex', flexDirection: 'column', gap: 12, opacity: enrichingPage ? 0.85 : 1, transition: 'opacity .15s' }}>
              {pageItems.map(p => (
                <ResultCard
                  key={p.id}
                  part={p}
                  isBestValue={p.id === bestId && tab !== 'new'}
                  onCompare={() => toggleCompare(p.id)}
                  compared={compared.includes(p.id)}
                  onSelect={() => setSelected(p.id)}
                  selected={selected === p.id}
                  onViewDetail={() => onViewDetail(p)}
                  onReport={onReport}
                />
              ))}
              {filtered.length === 0 && (
                <div style={{
                  background: '#fff', border: '1px dashed var(--border-strong)', borderRadius: 12,
                  padding: 36, textAlign: 'center', color: 'var(--ink-3)',
                }}>
                  No parts match your filters. Try adjusting your criteria.
                </div>
              )}
            </div>

            {totalPages > 1 && (
              <Pagination
                page={page}
                totalPages={totalPages}
                onChange={(p) => { setPage(p); window.scrollTo({ top: 0, behavior: 'smooth' }); }}
                loading={enrichingPage}
              />
            )}

            <div style={{ marginTop: 22, fontSize: 12, color: 'var(--ink-3)' }}>
              Showing live listings from eBay UK ·{' '}
              <a href="#" onClick={(e) => { e.preventDefault(); onSearch(); }} style={{ color: 'var(--accent-ink)', fontWeight: 600 }}>Refresh</a>
            </div>
          </section>
        </div>
      </div>

      <CompareDrawer
        items={comparedItems}
        onClose={onClearCompare}
        onRemove={toggleCompare}
        partName={shownPart}
      />
    </main>
  );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<FindMyPartApp />);
