/* ============================================================================
   Found Wheels — AI Assistant ("Concierge") version.
   A chat experience where Claude replies with rich inline "generative UI" cards
   (price analysis, car cards, comparison tables, checklists, recommendations,
   approvals) modeled on the Tool UI component pattern (assistant-ui/tool-ui),
   restyled in the Found Wheels visual language.

   Shares the same garage + profile as the app version. Reads shared helpers
   from window.FW (set by the main script) and exports window.AssistantScreen.
   ========================================================================== */
(function () {
  const { useState, useEffect, useRef } = React;

  // Lazy accessors — window.FW is populated by the main inline script, which
  // runs after this file is parsed but before anything here is rendered.
  const FW = () => window.FW || {};
  const money = (n) => (typeof n === 'number' && isFinite(n)) ? FW().fmt(Math.round(n)) : '—';
  const milesN = (n) => (typeof n === 'number' && isFinite(n)) ? FW().fmtN(n) + ' mi' : '—';

  // ── Persona + response contract handed to Claude on every turn ──────────────
  const SYSTEM = `You are the Found Wheels Concierge — a warm, sharp, friendly used-car buying assistant. You help people decide whether a used car is a smart buy. You are encouraging and plain-spoken, never pushy. Keep written replies SHORT (1–3 sentences) — let the cards carry the detail.

You reply with rich inline UI cards instead of walls of text. Respond with ONLY a single JSON object, no markdown fences, in this exact shape:
{
  "message": "your short conversational reply",
  "cards": [ ...zero or more card objects... ],
  "suggestions": [ "short follow-up the user might tap", ... up to 3 ]
}

CARD TYPES (emit only what's useful; most replies need 0–2 cards):

• price_analysis — "is this a good price?" / valuing a specific car.
  { "type":"price_analysis", "year":2019, "make":"Honda", "model":"Accord", "trim":"EX-L", "mileage":52000, "asking_price":21500, "vin":"optional" }
  IMPORTANT: never state prices yourself — emit this card and the app computes real market value, comps, and fair value. mileage helps a lot; ask for it if missing.

• car_result — present ONE specific car already in the conversation (e.g. a car the user named). The app fills the fair-value badge.
  { "type":"car_result", "year":2020, "make":"Toyota", "model":"Camry", "trim":"SE", "mileage":40000, "price":22000 }

• find_listings — show REAL active listings for sale (live dealer inventory). Use this WHENEVER the user wants to find / shop / browse / "show me" cars.
  { "type":"find_listings", "make":"Toyota", "model":"Camry", "year":2020, "max_price":22000, "min_price":12000, "max_mileage":80000, "zip":"94110" }
  make + model are REQUIRED; everything else is optional (omit year to search all years). For a broad ask like "a reliable sedan under $20k", emit 2–3 find_listings cards for specific models you'd recommend. The app fetches real inventory (price, mileage, dealer, location, link); if no listings backend is connected it shows the typical market price instead. Never invent listings yourself.

• comparison — side-by-side table of cars ALREADY in the garage.
  { "type":"comparison", "car_ids":[1,2] }   // ids from the garage context; omit car_ids to compare all
  Only emit if the garage has 2+ cars.

• recommendation — "which should I pick?". { "type":"recommendation" }  Only if garage has 1+ cars. The app scores against the user's profile.

• checklist — model-specific pre-purchase inspection points. YOU fill the items from your expertise.
  { "type":"checklist", "year":2019, "make":"Honda", "model":"Accord", "items":["Check the 1.5T for oil dilution — smell the dipstick for fuel","...5–7 specific items..."] }

• approval — propose adding a car to the garage; REQUIRED confirmation step before anything is added.
  { "type":"approval", "car":{ "year":2019, "make":"Honda", "model":"Accord", "trim":"EX-L", "mileage":52000, "price":21500, "vin":"optional" } }
  Whenever the user wants to add/save/track a car, emit this — never assume it's added.

• negotiation — draft an offer message to a seller. YOU write the draft.
  { "type":"negotiation", "ymm":"2018 BMW 330i", "asking_price":24900, "tone":"friendly", "draft":"Hi — I'm interested in your 330i..." }

• option_list — let the user pick from choices; tapping sends the "send" text as their next message.
  { "type":"option_list", "title":"What matters most?", "options":[ {"label":"Lowest price","sublabel":"Best deal","send":"Show me the cheapest option"}, ... ] }

RULES:
- The garage holds at most 3 cars. comparison/recommendation only make sense with garage cars.
- Reference garage cars by the ids given in CONTEXT.
- Don't invent prices, mileage, accident records, or specs you weren't given — use the cards (the app supplies real data) or ask.
- ACKNOWLEDGE what the user gave you. If they provide some details but not all, use what you have (emit the relevant card with the fields you know) and ask ONLY for the specific missing piece — never re-ask for information already in the conversation. If a value looks like a placeholder (e.g. "[price]", "VIN here", "N/A"), treat it as not-yet-provided and gently point out you need the real value.
- For general advice, a plain "message" with no cards is perfect.
- Always return valid JSON. No prose outside the JSON.`;

  function cardSummary(c) {
    switch (c.type) {
      case 'price_analysis': return `price analysis: ${c.year} ${c.make} ${c.model}`;
      case 'car_result': return `car: ${c.year} ${c.make} ${c.model}`;
      case 'find_listings': return `live listings: ${[c.year, c.make, c.model].filter(Boolean).join(' ')}`;
      case 'comparison': return `comparison table`;
      case 'recommendation': return `recommendation`;
      case 'checklist': return `inspection checklist: ${c.make} ${c.model}`;
      case 'approval': return `add-to-garage prompt`;
      case 'negotiation': return `offer draft`;
      case 'option_list': return `options`;
      default: return c.type;
    }
  }

  function parseJSON(raw) {
    if (!raw) return null;
    let s = String(raw).trim();
    // strip code fences
    s = s.replace(/^```(?:json)?\s*/i, '').replace(/\s*```$/i, '');
    try { return JSON.parse(s); } catch {}
    // grab outermost { ... }
    const a = s.indexOf('{'), b = s.lastIndexOf('}');
    if (a >= 0 && b > a) { try { return JSON.parse(s.slice(a, b + 1)); } catch {} }
    return null;
  }

  // Deterministic synthetic VIN so the pricing pipeline's mock path is stable for
  // a given year/make/model (real backend, when configured, will reject it and the
  // pipeline falls back to the same deterministic mock).
  function synthVin(year, make, model) {
    const base = `${year || ''}${make || ''}${model || ''}`.toUpperCase().replace(/[^A-Z0-9]/g, '');
    let s = (base + 'FOUNDWHEELS00000000').slice(0, 17);
    return s.replace(/[IOQ]/g, '0');
  }

  // ── Agent turn: ask Claude, parse, return {message, cards(raw), suggestions} ──
  async function askClaude({ userText, history, cars, profile }) {
    if (!(window.claude && window.claude.complete)) {
      return {
        message: "I need an AI connection to chat, and it isn't available here. You can still switch to the app to add cars, compare them, and get a recommendation.",
        cards: [], suggestions: [],
      };
    }
    const garageCtx = cars.map(c => ({
      id: c.id, year: c.year, make: c.make, model: c.model, trim: c.trim,
      mileage: c.mileage, price: c.price, market_avg: c.market_avg, mpg_hwy: c.mpg_hwy, fuel: c.fuel,
    }));
    const profileCtx = profile ? { budget: profile.budget, use: profile.use, priorities: profile.priorities } : null;
    const convo = history.slice(-8).map(m =>
      m.role === 'user'
        ? `User: ${m.text}`
        : `Assistant: ${(m.text || '')}${m.cards && m.cards.length ? ' [' + m.cards.map(cardSummary).join('; ') + ']' : ''}`
    ).join('\n');

    const prompt = `${SYSTEM}

CONTEXT
Profile: ${JSON.stringify(profileCtx)}
Garage (${cars.length}/3 cars): ${JSON.stringify(garageCtx)}

CONVERSATION
${convo}
User: ${userText}

Respond with ONLY the JSON object.`;

    let raw = '';
    try { raw = await window.claude.complete(prompt); }
    catch (e) { return { message: "Something hiccuped reaching the assistant — mind trying that again?", cards: [], suggestions: [] }; }

    const parsed = parseJSON(raw);
    if (!parsed) return { message: String(raw || '').slice(0, 600) || "Sorry, I didn't catch that.", cards: [], suggestions: [] };
    return {
      message: parsed.message || '',
      cards: Array.isArray(parsed.cards) ? parsed.cards : [],
      suggestions: Array.isArray(parsed.suggestions) ? parsed.suggestions.slice(0, 3) : [],
    };
  }

  // Live inventory search via the backend /search endpoint (real listings).
  async function findListings(card) {
    const base = (FW().normalizeBackendUrl ? FW().normalizeBackendUrl(window.__BACKEND_URL || '') : (window.__BACKEND_URL || '')) || '';
    if (!base) return { configured: false, listings: [] };
    const qs = new URLSearchParams({ make: card.make || '', model: card.model || '' });
    if (card.year) qs.set('year', card.year);
    qs.set('zip', card.zip || '94110');
    if (card.max_price) qs.set('maxPrice', card.max_price);
    if (card.min_price) qs.set('minPrice', card.min_price);
    if (card.max_mileage) qs.set('maxMileage', card.max_mileage);
    try {
      const r = await fetch(`${base.replace(/\/$/, '')}/search?${qs.toString()}`);
      const data = await r.json();
      if (r.ok && data && !data.error) return { configured: true, ...data };
      return { configured: true, error: data.error || ('HTTP ' + r.status), listings: [] };
    } catch (e) { return { configured: true, error: e.message, listings: [] }; }
  }

  // ── Resolve raw card intents against real app data/pipelines ────────────────
  async function resolveCard(card, { cars, profile }) {
    const t = card && card.type;
    try {
      if (t === 'price_analysis') {
        const { year, make, model, trim, mileage, asking_price, vin } = card;
        const md = await FW().lookupRealPricing({
          vin: vin || synthVin(year, make, model),
          year, make, model, mileage: parseInt(mileage) || 0, zip: '94110',
        });
        return { type: 'price_analysis', year, make, model, trim, mileage: parseInt(mileage) || null, asking_price: parseInt(asking_price) || null, data: md };
      }
      if (t === 'find_listings') {
        if (!card.make || !card.model) return null;
        const res = await findListings(card);
        const est = FW().estimateMarketPrice(card.year || 0, card.make, card.model, 60000) || {};
        return { type: 'find_listings', query: { year: card.year, make: card.make, model: card.model, max_price: card.max_price, min_price: card.min_price, max_mileage: card.max_mileage }, res, est };
      }
      if (t === 'car_result') {
        const { year, make, model, trim, mileage, price, vin } = card;
        const est = FW().estimateMarketPrice(year, make, model, parseInt(mileage) || 0) || {};
        return { type: 'car_result', year, make, model, trim, mileage: parseInt(mileage) || null, price: parseInt(price) || null, vin, est };
      }
      if (t === 'comparison') {
        const ids = Array.isArray(card.car_ids) ? card.car_ids : [];
        const list = (ids.length ? ids.map(id => cars.find(c => c.id === id)).filter(Boolean) : cars).slice(0, 3);
        if (list.length < 2) return null;
        return { type: 'comparison', cars: list };
      }
      if (t === 'recommendation') {
        if (!cars.length) return null;
        const scored = cars.map(c => ({ car: c, score: FW().scoreCarForProfile(c, profile) })).sort((a, b) => b.score - a.score);
        return { type: 'recommendation', scored };
      }
      if (t === 'checklist') {
        let items = Array.isArray(card.items) ? card.items.filter(Boolean) : [];
        if (!items.length) items = FW().findModelFallback(card.make, card.model) || [];
        if (!items.length) return null;
        return { type: 'checklist', year: card.year, make: card.make, model: card.model, items };
      }
      if (t === 'approval') {
        if (!card.car) return null;
        return { type: 'approval', car: card.car };
      }
      if (t === 'negotiation') {
        return { type: 'negotiation', ymm: card.ymm, asking_price: parseInt(card.asking_price) || null, tone: card.tone, draft: card.draft || '' };
      }
      if (t === 'option_list') {
        const opts = Array.isArray(card.options) ? card.options.filter(o => o && o.label) : [];
        if (!opts.length) return null;
        return { type: 'option_list', title: card.title || '', options: opts };
      }
    } catch (e) { return null; }
    return null;
  }

  /* ========================== Tool UI card components ======================= */

  const Badge = ({ tone = 'neutral', children }) => {
    const map = {
      green: { bg: 'var(--green-light)', fg: 'var(--green)' },
      amber: { bg: 'var(--amber-light)', fg: 'var(--amber)' },
      red: { bg: 'var(--red-light)', fg: 'var(--red)' },
      accent: { bg: 'var(--accent-light)', fg: 'var(--accent)' },
      neutral: { bg: 'var(--accent-light)', fg: 'var(--text2)' },
    }[tone] || {};
    return <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5, fontSize: 11.5, fontWeight: 700, letterSpacing: '0.01em', padding: '3px 9px', borderRadius: 999, background: map.bg, color: map.fg }}>{children}</span>;
  };

  const CardShell = ({ children, label }) => (
    <div style={{ border: '1px solid var(--border)', background: 'var(--card)', borderRadius: 16, overflow: 'hidden', boxShadow: 'var(--shadow)' }}>
      {label && <div style={{ padding: '10px 16px', borderBottom: '1px solid var(--border)', fontSize: 11, fontWeight: 700, letterSpacing: '0.07em', textTransform: 'uppercase', color: 'var(--text3)' }}>{label}</div>}
      {children}
    </div>
  );

  function ModelPhoto({ year, make, model, h = 132 }) {
    const [url, setUrl] = useState(null);
    const [failed, setFailed] = useState(false);
    useEffect(() => {
      let on = true;
      setFailed(false);
      if (FW().fetchModelPhoto) FW().fetchModelPhoto({ year, make, model }).then(u => { if (on) setUrl(u); }).catch(() => {});
      return () => { on = false; };
    }, [year, make, model]);
    const label = [year, make, model].filter(Boolean).join(' ');
    return (
      <div style={{ height: h, background: 'var(--accent-light)', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 8, overflow: 'hidden' }}>
        {(url && !failed)
          ? <img src={url} alt={`${make} ${model}`} style={{ width: '100%', height: '100%', objectFit: 'cover' }} onError={() => setFailed(true)} />
          : <>
              <svg width={h > 80 ? 46 : 30} height={h > 80 ? 46 : 30} viewBox="0 0 24 24" fill="none" style={{ opacity: 0.4 }}><path d="M3 13l2-5 7 2 6-5 3 3" stroke="var(--text2)" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" /><circle cx="7" cy="17" r="1.6" fill="var(--text2)" /><circle cx="17" cy="17" r="1.6" fill="var(--text2)" /></svg>
              {h > 80 && label && <div style={{ fontSize: 11, fontWeight: 700, color: 'var(--text3)', letterSpacing: '0.02em' }}>{label}</div>}
            </>}
      </div>
    );
  }

  // Deal verdict from asking price vs fair value (or market avg).
  function dealVerdict(price, fair) {
    if (!price || !fair) return null;
    const d = (price - fair) / fair;
    if (d <= -0.05) return { tone: 'green', label: 'Good deal', detail: `${money(fair - price)} below fair value` };
    if (d <= 0.05) return { tone: 'accent', label: 'Fair price', detail: 'Right around market value' };
    if (d <= 0.15) return { tone: 'amber', label: 'A bit high', detail: `${money(price - fair)} over fair value` };
    return { tone: 'red', label: 'Overpriced', detail: `${money(price - fair)} over fair value` };
  }

  function CarResultCard({ card, ctx }) {
    const { year, make, model, trim, mileage, price, est } = card;
    const fair = est.fair_value || est.market_avg;
    const verdict = dealVerdict(price, fair);
    const [state, setState] = useState('idle'); // idle | confirm | adding | added | full
    const [addedId, setAddedId] = useState(null);
    const add = async () => {
      if (ctx.cars.length >= 3) { setState('full'); return; }
      setState('adding');
      const c = await FW().buildCarFromBasics({ year, make, model, trim, mileage, price, vin: card.vin });
      ctx.addCar(c); setAddedId(c.id); setState('added');
    };
    return (
      <CardShell>
        <div style={{ display: 'flex', flexDirection: 'column' }}>
          <ModelPhoto year={year} make={make} model={model} />
          <div style={{ padding: 16 }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 }}>
              <div>
                <div style={{ fontSize: 16, fontWeight: 800, letterSpacing: '-0.01em' }}>{year} {make} {model}</div>
                <div style={{ fontSize: 13, color: 'var(--text2)', marginTop: 2 }}>{[trim, mileage ? milesN(mileage) : null].filter(Boolean).join(' · ')}</div>
              </div>
              <div style={{ textAlign: 'right' }}>
                <div style={{ fontSize: 20, fontWeight: 800, letterSpacing: '-0.02em' }}>{money(price)}</div>
              </div>
            </div>
            {verdict && <div style={{ marginTop: 12, display: 'flex', alignItems: 'center', gap: 8 }}><Badge tone={verdict.tone}>{verdict.label}</Badge><span style={{ fontSize: 12, color: 'var(--text3)' }}>{verdict.detail}</span></div>}
            <div style={{ marginTop: 14, display: 'flex', gap: 8 }}>
              {state === 'added'
                ? <><Badge tone="green">✓ Added to garage</Badge>{addedId != null && <MiniBtn onClick={() => ctx.onViewCar(addedId)} primary>Open →</MiniBtn>}</>
                : state === 'full'
                  ? <span style={{ fontSize: 12.5, color: 'var(--red)', fontWeight: 600 }}>Garage is full (3/3) — remove one first.</span>
                  : state === 'confirm'
                    ? <><MiniBtn onClick={add} primary>Confirm add</MiniBtn><MiniBtn onClick={() => setState('idle')}>Cancel</MiniBtn></>
                    : <><MiniBtn onClick={() => setState('confirm')} primary>{state === 'adding' ? 'Adding…' : 'Add to garage'}</MiniBtn>
                      <MiniBtn onClick={() => ctx.onSend(`Is the ${year} ${make} ${model}${trim ? ' ' + trim : ''} a good price at ${money(price)}${mileage ? ' with ' + milesN(mileage) : ''}?`)}>Analyze price</MiniBtn></>}
            </div>
          </div>
        </div>
      </CardShell>
    );
  }

  // Listing photo: prefer the listing's own photo; otherwise fall back to a real
  // representative photo of that year/make/model (backend /image → Wikimedia), the
  // same source used across the app. Better than a blank placeholder.
  function ListingPhoto({ l }) {
    const [url, setUrl] = useState(l.photo || null);
    useEffect(() => {
      let on = true;
      if (!l.photo && FW().fetchModelPhoto) {
        FW().fetchModelPhoto({ year: l.year, make: l.make, model: l.model }).then(u => { if (on && u) setUrl(u); }).catch(() => {});
      }
      return () => { on = false; };
    }, [l.photo, l.year, l.make, l.model]);
    return url
      ? <img src={url} alt={`${l.make} ${l.model}`} style={{ width: '100%', height: '100%', objectFit: 'cover' }} onError={() => setUrl(null)} />
      : <svg width="24" height="24" viewBox="0 0 24 24" fill="none" style={{ opacity: 0.4 }}><path d="M3 13l2-5 7 2 6-5 3 3" stroke="var(--text2)" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" /><circle cx="7" cy="17" r="1.5" fill="var(--text2)" /><circle cx="17" cy="17" r="1.5" fill="var(--text2)" /></svg>;
  }

  // Best click-through for a listing: the real dealer page when present, else a
  // web search (by VIN if we have one — that usually surfaces the exact car).
  function listingLink(l) {
    if (l.url) return { href: l.url, label: 'View listing ↗', real: true };
    const q = [l.year, l.make, l.model, l.trim, l.vin ? ('VIN ' + l.vin) : '', 'for sale'].filter(Boolean).join(' ');
    return { href: 'https://www.bing.com/search?q=' + encodeURIComponent(q), label: 'Find this car ↗', real: false };
  }

  function ListingRow({ l, fair, ctx, first }) {
    const verdict = dealVerdict(l.price, fair);
    const [state, setState] = useState('idle'); // idle | adding | added | full
    const [addedId, setAddedId] = useState(null);
    const add = async () => {
      if (ctx.cars.length >= 3) { setState('full'); return; }
      setState('adding');
      const car = await FW().buildCarFromBasics({ year: l.year, make: l.make, model: l.model, trim: l.trim, mileage: l.mileage, price: l.price, vin: l.vin && l.vin.length === 17 ? l.vin : undefined });
      ctx.addCar(car); setAddedId(car.id); setState('added');
    };
    const loc = [l.city, l.state].filter(Boolean).join(', ');
    return (
      <div style={{ display: 'flex', gap: 12, padding: '13px 16px', borderTop: first ? 'none' : '1px solid var(--border)' }}>
        <div style={{ width: 84, height: 62, borderRadius: 10, overflow: 'hidden', flexShrink: 0, background: 'var(--accent-light)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
          <ListingPhoto l={l} />
        </div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', gap: 8 }}>
            <div style={{ fontSize: 14, fontWeight: 800, letterSpacing: '-0.01em' }}>{l.year} {l.make} {l.model}</div>
            <div style={{ fontSize: 15, fontWeight: 800, whiteSpace: 'nowrap', fontVariantNumeric: 'tabular-nums' }}>{money(l.price)}</div>
          </div>
          <div style={{ fontSize: 12, color: 'var(--text2)', marginTop: 2 }}>{[l.trim, l.mileage ? milesN(l.mileage) : null].filter(Boolean).join(' · ')}</div>
          <div style={{ fontSize: 11.5, color: 'var(--text3)', marginTop: 2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{[l.dealer, loc].filter(Boolean).join(' · ')}</div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 8, flexWrap: 'wrap' }}>
            {verdict && <Badge tone={verdict.tone}>{verdict.label}</Badge>}
            {(() => { const lk = listingLink(l); return <a href={lk.href} target="_blank" rel="noopener noreferrer" title={lk.real ? 'Open the dealer listing' : 'Search the web for this car'} style={{ fontSize: 12.5, fontWeight: 700, color: 'var(--accent)', textDecoration: 'none' }}>{lk.label}</a>; })()}
            {state === 'added'
              ? <span style={{ display: 'inline-flex', gap: 8, alignItems: 'center' }}><span style={{ fontSize: 12, fontWeight: 700, color: 'var(--green)' }}>✓ In garage</span>{addedId != null && <button onClick={() => ctx.onViewCar(addedId)} style={{ fontSize: 12.5, fontWeight: 700, color: 'var(--accent)', background: 'none', border: 'none', cursor: 'pointer', padding: 0 }}>Open →</button>}</span>
              : state === 'full'
                ? <span style={{ fontSize: 12, color: 'var(--red)', fontWeight: 600 }}>Garage full</span>
                : <button onClick={add} style={{ fontSize: 12.5, fontWeight: 700, color: 'var(--text2)', background: 'none', border: 'none', cursor: 'pointer', padding: 0 }}>{state === 'adding' ? 'Adding…' : '+ Add to garage'}</button>}
          </div>
        </div>
      </div>
    );
  }

  function ListingsCard({ card, ctx }) {
    const { res, est, query } = card;
    const ymm = [query.year, query.make, query.model].filter(Boolean).join(' ');
    const fair = (res && (res.market_median || res.market_avg)) || est.fair_value || est.market_avg;

    // No backend connected — be honest: show typical market price, not fake listings.
    if (!res || res.configured === false) {
      return (
        <CardShell label={`Market price · ${ymm}`}>
          <div style={{ padding: 16 }}>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8, marginBottom: 12 }}>
              <Metric label="Typical price" value={money(est.market_avg)} big highlight />
              <Metric label="Range" value={`${money(est.market_low)}–${money(est.market_high)}`} />
            </div>
            <div style={{ fontSize: 12.5, color: 'var(--text3)', lineHeight: 1.5 }}>This is an estimated market price. Connect a listings backend to pull real cars for sale near you.</div>
            <button onClick={() => FW().openConnect && FW().openConnect()} style={{ marginTop: 12, padding: '8px 14px', borderRadius: 10, border: 'none', background: 'var(--accent)', color: '#fff', fontWeight: 700, fontSize: 12.5, cursor: 'pointer' }}>Connect live data →</button>
          </div>
        </CardShell>
      );
    }

    const listings = res.listings || [];
    if (!listings.length) {
      return (
        <CardShell label={`Live listings · ${ymm}`}>
          <div style={{ padding: 16 }}>
            <div style={{ fontSize: 13.5, color: 'var(--text)', marginBottom: 10 }}>No active listings matched those filters right now.</div>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
              <Metric label="Typical price" value={money(est.market_avg)} />
              <Metric label="Range" value={`${money(est.market_low)}–${money(est.market_high)}`} />
            </div>
          </div>
        </CardShell>
      );
    }

    return (
      <CardShell label={`Live listings · ${ymm}`}>
        <div>
          {listings.map((l, i) => <ListingRow key={i} l={l} fair={fair} ctx={ctx} first={i === 0} />)}
        </div>
        <div style={{ padding: '9px 16px', borderTop: '1px solid var(--border)', fontSize: 11, color: 'var(--text3)', display: 'flex', justifyContent: 'space-between' }}>
          <span>{res.count} found · sorted by price</span>
          <span>● live · {res.source}</span>
        </div>
      </CardShell>
    );
  }

  const MiniBtn = ({ children, onClick, primary }) => (
    <button onClick={onClick} style={{
      padding: '8px 14px', borderRadius: 999, fontSize: 13, fontWeight: 700, cursor: 'pointer',
      border: primary ? 'none' : '1.5px solid var(--border)',
      background: primary ? 'var(--accent)' : 'transparent',
      color: primary ? '#fff' : 'var(--text)',
    }}>{children}</button>
  );

  function Metric({ label, value, big, highlight }) {
    return (
      <div style={{ padding: '12px 14px', background: highlight ? 'var(--accent-light)' : 'transparent', borderRadius: 12 }}>
        <div style={{ fontSize: 10.5, fontWeight: 700, letterSpacing: '0.06em', textTransform: 'uppercase', color: highlight ? 'var(--accent)' : 'var(--text3)' }}>{label}</div>
        <div style={{ fontSize: big ? 24 : 18, fontWeight: 800, letterSpacing: '-0.02em', marginTop: 3, color: highlight ? 'var(--accent)' : 'var(--text)', fontVariantNumeric: 'tabular-nums' }}>{value}</div>
      </div>
    );
  }

  function PriceAnalysisCard({ card, ctx }) {
    const d = card.data || {};
    const fair = d.fair_value || d.market_median || d.market_avg;
    const verdict = dealVerdict(card.asking_price, fair);
    const confTone = { high: 'green', medium: 'amber', low: 'amber' }[d.confidence] || 'neutral';
    const real = d.source && d.source !== 'mock';
    return (
      <CardShell label={`Price analysis · ${card.year} ${card.make} ${card.model}${card.trim ? ' ' + card.trim : ''}`}>
        <div style={{ padding: 16 }}>
          {card.asking_price && verdict && (
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, padding: '12px 14px', borderRadius: 12, background: `var(--${verdict.tone === 'accent' ? 'accent' : verdict.tone}-light)`, marginBottom: 14 }}>
              <div>
                <div style={{ fontSize: 13, color: 'var(--text2)', fontWeight: 600 }}>Asking {money(card.asking_price)}</div>
                <div style={{ fontSize: 17, fontWeight: 800, marginTop: 2 }}>{verdict.label}</div>
              </div>
              <div style={{ fontSize: 12.5, color: 'var(--text2)', textAlign: 'right', maxWidth: 150 }}>{verdict.detail}</div>
            </div>
          )}
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
            <Metric label="Fair value" value={money(fair)} big highlight />
            <Metric label="Market avg" value={money(d.market_avg)} big />
            <Metric label="Low" value={money(d.market_low)} />
            <Metric label="High" value={money(d.market_high)} />
          </div>
          <div style={{ marginTop: 14, display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 8, fontSize: 12, color: 'var(--text3)' }}>
            <Badge tone={confTone}>{(d.confidence || 'low')} confidence</Badge>
            <span>{d.num_comps || d.num_used || 0} comps{d.radius_mi ? ` · ${d.radius_mi === 'nationwide' || d.radius_mi === 0 ? 'nationwide' : d.radius_mi + ' mi'}` : ''}</span>
            {d.mileage_adjusted && <span>· mileage-adjusted</span>}
            <span style={{ marginLeft: 'auto' }}>{real ? '● live data' : '○ estimated'}</span>
          </div>
        </div>
      </CardShell>
    );
  }

  function ComparisonCard({ card, ctx }) {
    const cars = card.cars;
    const tco = (c) => {
      const repairs = (c.repairs || []).reduce((a, r) => a + r.cost, 0);
      return c.price + repairs + (c.fuel_monthly || 0) * 60 + (c.insurance_monthly || 0) * 60 + (c.registration_annual || 0) * 5;
    };
    const rows = [
      { k: 'Price', f: c => money(c.price), best: c => -c.price },
      { k: 'Mileage', f: c => milesN(c.mileage), best: c => -c.mileage },
      { k: '5-yr cost', f: c => money(tco(c)), best: c => -tco(c) },
      { k: 'MPG hwy', f: c => `${c.mpg_hwy || '—'}`, best: c => c.mpg_hwy || 0 },
      { k: 'Fit score', f: c => `${FW().scoreCarForProfile(c, ctx.profile)}`, best: c => FW().scoreCarForProfile(c, ctx.profile) },
    ];
    return (
      <CardShell label="Comparison">
        <div style={{ overflowX: 'auto' }}>
          <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}>
            <thead>
              <tr>
                <th style={{ textAlign: 'left', padding: '10px 14px', fontSize: 11, fontWeight: 700, color: 'var(--text3)', textTransform: 'uppercase', letterSpacing: '0.05em' }}></th>
                {cars.map(c => <th key={c.id} style={{ textAlign: 'left', padding: '10px 14px', fontWeight: 800, fontSize: 13, borderLeft: '1px solid var(--border)' }}>{c.year} {c.make}<br /><span style={{ fontWeight: 600, color: 'var(--text2)' }}>{c.model}</span></th>)}
              </tr>
            </thead>
            <tbody>
              {rows.map((row, ri) => {
                const vals = cars.map(row.best);
                const max = Math.max(...vals);
                return (
                  <tr key={row.k} style={{ borderTop: '1px solid var(--border)' }}>
                    <td style={{ padding: '10px 14px', color: 'var(--text2)', fontWeight: 600, whiteSpace: 'nowrap' }}>{row.k}</td>
                    {cars.map((c, ci) => {
                      const isBest = cars.length > 1 && vals[ci] === max && vals.filter(v => v === max).length === 1;
                      return <td key={c.id} style={{ padding: '10px 14px', borderLeft: '1px solid var(--border)', fontVariantNumeric: 'tabular-nums', fontWeight: isBest ? 800 : 500, color: isBest ? 'var(--green)' : 'var(--text)' }}>{row.f(c)}{isBest ? ' ✓' : ''}</td>;
                    })}
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
        <div style={{ padding: '10px 14px', borderTop: '1px solid var(--border)' }}>
          <MiniBtn onClick={() => ctx.onSend('Based on this comparison, which one should I pick?')} primary>Which should I pick?</MiniBtn>
        </div>
      </CardShell>
    );
  }

  function RecommendationCard({ card, ctx }) {
    const scored = card.scored;
    const win = scored[0];
    const c = win.car;
    return (
      <CardShell label="Recommendation">
        <div style={{ padding: 16 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
            <div style={{ width: 56, height: 56, borderRadius: 12, overflow: 'hidden', flexShrink: 0 }}><ModelPhoto year={c.year} make={c.make} model={c.model} h={56} /></div>
            <div style={{ flex: 1 }}>
              <Badge tone="green">Top pick</Badge>
              <div style={{ fontSize: 17, fontWeight: 800, marginTop: 5, letterSpacing: '-0.01em' }}>{c.year} {c.make} {c.model}</div>
            </div>
            <div style={{ textAlign: 'right' }}>
              <div style={{ fontSize: 26, fontWeight: 800, color: 'var(--accent)', lineHeight: 1 }}>{win.score}</div>
              <div style={{ fontSize: 10.5, color: 'var(--text3)', fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.05em' }}>fit score</div>
            </div>
          </div>
          {scored.length > 1 && (
            <div style={{ marginTop: 14, paddingTop: 12, borderTop: '1px solid var(--border)', display: 'flex', flexDirection: 'column', gap: 8 }}>
              {scored.slice(1).map(s => (
                <div key={s.car.id} style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13 }}>
                  <span style={{ color: 'var(--text2)' }}>{s.car.year} {s.car.make} {s.car.model}</span>
                  <span style={{ fontWeight: 700, fontVariantNumeric: 'tabular-nums' }}>{s.score}</span>
                </div>
              ))}
            </div>
          )}
          <div style={{ marginTop: 14 }}>
            <MiniBtn onClick={() => ctx.onViewCar(c.id)} primary>Open {c.make} {c.model} →</MiniBtn>
          </div>
        </div>
      </CardShell>
    );
  }

  function ChecklistCard({ card }) {
    return (
      <CardShell label={`Inspection checklist · ${[card.year, card.make, card.model].filter(Boolean).join(' ')}`}>
        <div style={{ padding: '14px 16px', display: 'flex', flexDirection: 'column', gap: 11 }}>
          {card.items.map((it, i) => (
            <div key={i} style={{ display: 'flex', gap: 10, alignItems: 'flex-start' }}>
              <div style={{ width: 18, height: 18, borderRadius: 6, border: '1.5px solid var(--accent)', flexShrink: 0, marginTop: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                <span style={{ fontSize: 10, fontWeight: 800, color: 'var(--accent)' }}>{i + 1}</span>
              </div>
              <span style={{ fontSize: 13.5, lineHeight: 1.5, color: 'var(--text)' }}>{it}</span>
            </div>
          ))}
        </div>
      </CardShell>
    );
  }

  function ApprovalCard({ card, ctx }) {
    const c = card.car;
    const [state, setState] = useState('idle'); // idle | adding | added | full
    const [addedId, setAddedId] = useState(null);
    const confirm = async () => {
      if (ctx.cars.length >= 3) { setState('full'); return; }
      setState('adding');
      const built = await FW().buildCarFromBasics(c);
      ctx.addCar(built); setAddedId(built.id); setState('added');
    };
    return (
      <CardShell label="Add to garage?">
        <div style={{ padding: 16 }}>
          <div style={{ fontSize: 16, fontWeight: 800 }}>{c.year} {c.make} {c.model}{c.trim ? <span style={{ fontWeight: 600, color: 'var(--text2)' }}> {c.trim}</span> : null}</div>
          <div style={{ fontSize: 13, color: 'var(--text2)', marginTop: 3 }}>{[c.mileage ? milesN(parseInt(String(c.mileage).replace(/\D/g, ''))) : null, c.price ? money(parseInt(String(c.price).replace(/\D/g, ''))) : null].filter(Boolean).join(' · ')}</div>
          <div style={{ marginTop: 14 }}>
            {state === 'added'
              ? <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}><Badge tone="green">✓ Added to garage</Badge>{addedId != null && <MiniBtn onClick={() => ctx.onViewCar(addedId)} primary>Open →</MiniBtn>}</div>
              : state === 'full'
                ? <span style={{ fontSize: 12.5, color: 'var(--red)', fontWeight: 600 }}>Garage is full (3/3) — remove one first.</span>
                : <div style={{ display: 'flex', gap: 8 }}>
                  <MiniBtn onClick={confirm} primary>{state === 'adding' ? 'Adding…' : 'Add to garage'}</MiniBtn>
                  <MiniBtn onClick={() => setState('added')}>Not now</MiniBtn>
                </div>}
          </div>
        </div>
      </CardShell>
    );
  }

  function NegotiationCard({ card }) {
    const [copied, setCopied] = useState(false);
    const copy = () => { try { navigator.clipboard.writeText(card.draft); setCopied(true); setTimeout(() => setCopied(false), 1600); } catch {} };
    return (
      <CardShell label={`Offer draft${card.tone ? ' · ' + card.tone : ''}`}>
        <div style={{ padding: 16 }}>
          {(card.ymm || card.asking_price) && <div style={{ fontSize: 12.5, color: 'var(--text3)', marginBottom: 10 }}>{[card.ymm, card.asking_price ? `asking ${money(card.asking_price)}` : null].filter(Boolean).join(' · ')}</div>}
          <div style={{ fontSize: 13.5, lineHeight: 1.6, whiteSpace: 'pre-wrap', padding: '12px 14px', background: 'var(--accent-light)', borderRadius: 12, color: 'var(--text)' }}>{card.draft}</div>
          <div style={{ marginTop: 12 }}><MiniBtn onClick={copy} primary>{copied ? '✓ Copied' : 'Copy message'}</MiniBtn></div>
        </div>
      </CardShell>
    );
  }

  function OptionListCard({ card, ctx }) {
    return (
      <CardShell label={card.title || 'Pick one'}>
        <div style={{ display: 'flex', flexDirection: 'column' }}>
          {card.options.map((o, i) => (
            <button key={i} onClick={() => ctx.onSend(o.send || o.label)} style={{
              textAlign: 'left', padding: '13px 16px', background: 'transparent', border: 'none',
              borderTop: i ? '1px solid var(--border)' : 'none', cursor: 'pointer', display: 'flex',
              justifyContent: 'space-between', alignItems: 'center', gap: 10, color: 'var(--text)',
            }}
              onMouseEnter={e => e.currentTarget.style.background = 'var(--accent-light)'}
              onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
              <span>
                <span style={{ fontSize: 14, fontWeight: 700, display: 'block' }}>{o.label}</span>
                {o.sublabel && <span style={{ fontSize: 12.5, color: 'var(--text2)' }}>{o.sublabel}</span>}
              </span>
              <span style={{ color: 'var(--text3)', fontSize: 16 }}>→</span>
            </button>
          ))}
        </div>
      </CardShell>
    );
  }

  function CardRenderer({ card, ctx }) {
    switch (card.type) {
      case 'price_analysis': return <PriceAnalysisCard card={card} ctx={ctx} />;
      case 'car_result': return <CarResultCard card={card} ctx={ctx} />;
      case 'find_listings': return <ListingsCard card={card} ctx={ctx} />;
      case 'comparison': return <ComparisonCard card={card} ctx={ctx} />;
      case 'recommendation': return <RecommendationCard card={card} ctx={ctx} />;
      case 'checklist': return <ChecklistCard card={card} />;
      case 'approval': return <ApprovalCard card={card} ctx={ctx} />;
      case 'negotiation': return <NegotiationCard card={card} />;
      case 'option_list': return <OptionListCard card={card} ctx={ctx} />;
      default: return null;
    }
  }

  /* ============================ Chat shell ================================= */

  // Starter prompts, tailored to the onboarding profile. Picks a persona from the
  // user's priorities + primary use, then phrases every starter around it — so a
  // "high performance" shopper never sees Accord/sedan examples.
  function buildStarters(profile, carsCount) {
    const p = profile || {};
    const pri = Array.isArray(p.priorities) ? p.priorities : [];
    const use = p.use || '';
    const has = (s) => pri.some(x => String(x).toLowerCase().includes(s));

    const budgetPhrase = {
      'Under $15k': 'under $15k', '$15\u201320k': 'around $18k', '$20\u201325k': 'around $23k',
      '$25\u201330k': 'around $28k', 'Over $30k': 'around $35k',
    }[p.budget] || 'around $25k';

    let persona;
    if (has('performance') || has('track') || has('sport') || has('manual')) persona = 'performance';
    else if (has('electric') || has('hybrid')) persona = 'ev';
    else if (has('awd') || has('4wd')) persona = 'awd';
    else if (use === 'Family hauler') persona = 'family';
    else if (has('fuel efficient') || use === 'Daily commute' || use === 'Just errands') persona = 'efficient';
    else if (use === 'Weekend fun') persona = 'fun';
    else if (use === 'Road trips') persona = 'roadtrip';
    else persona = 'general';

    const P = {
      performance: {
        good: 'Is a 2020 BMW M340i a good buy at $38,000?',
        inspect: 'What should I inspect on a used Ford Mustang GT?',
        find: `Find me a fast performance car ${budgetPhrase}`,
        kind: 'performance car',
      },
      ev: {
        good: 'Is a 2021 Tesla Model 3 a good buy at $27,000?',
        inspect: 'What should I check on a used EV battery?',
        find: `Find me a solid used EV ${budgetPhrase}`,
        kind: 'EV',
      },
      awd: {
        good: 'Is a 2020 Subaru Outback a good buy at $24,000?',
        inspect: 'What should I inspect on a used AWD wagon or SUV?',
        find: `Find me an AWD vehicle ${budgetPhrase}`,
        kind: 'AWD car',
      },
      family: {
        good: 'Is a 2020 Toyota Highlander a good buy at $29,000?',
        inspect: 'What should I inspect on a used 3-row SUV?',
        find: `Find me a roomy family SUV ${budgetPhrase}`,
        kind: 'family SUV',
      },
      efficient: {
        good: 'Is a 2020 Toyota Corolla Hybrid a good buy at $19,000?',
        inspect: 'What should I inspect on a high-mileage Honda Civic?',
        find: `Find me a fuel-efficient commuter ${budgetPhrase}`,
        kind: 'commuter',
      },
      fun: {
        good: 'Is a 2019 Mazda MX-5 Miata a good buy at $23,000?',
        inspect: 'What should I inspect on a used sports car?',
        find: `Find me a fun weekend car ${budgetPhrase}`,
        kind: 'weekend car',
      },
      roadtrip: {
        good: 'Is a 2020 Honda Accord a good buy at $23,000?',
        inspect: 'What should I inspect on a high-mileage highway cruiser?',
        find: `Find me a comfortable highway cruiser ${budgetPhrase}`,
        kind: 'road-trip car',
      },
      general: {
        good: 'Is a 2019 Honda Accord EX-L a good buy at $21,500?',
        inspect: 'What should I inspect on a used BMW 330i?',
        find: `Find me a reliable car ${budgetPhrase}`,
        kind: 'car',
      },
    }[persona];

    const last = carsCount >= 2 ? 'Compare the cars in my garage'
      : carsCount === 1 ? 'How does the car in my garage stack up?'
      : `What matters most when buying a used ${P.kind}?`;
    return [P.good, P.inspect, P.find, last];
  }

  function Avatar() {
    return (
      <div style={{ width: 30, height: 30, borderRadius: 9, background: 'var(--accent)', flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
        <svg width="17" height="17" viewBox="0 0 24 24" fill="none"><path d="M4 14l2-6 8 2 7-6 3 4" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /><circle cx="8" cy="18" r="2" fill="#fff" /><circle cx="18" cy="18" r="2" fill="#fff" /></svg>
      </div>
    );
  }

  function ThinkingBubble() {
    const [dots, setDots] = useState(1);
    useEffect(() => { const t = setInterval(() => setDots(d => d % 3 + 1), 380); return () => clearInterval(t); }, []);
    return (
      <div style={{ display: 'flex', gap: 12, alignItems: 'flex-start' }}>
        <Avatar />
        <div style={{ padding: '12px 16px', borderRadius: 16, background: 'var(--card)', border: '1px solid var(--border)', color: 'var(--text2)', fontSize: 14 }}>
          Looking into that{'.'.repeat(dots)}
        </div>
      </div>
    );
  }

  // Perceived-streaming word reveal — animates a freshly-arrived reply once, then
  // renders statically on any re-render/reload (tracked by message id).
  const animatedMsgIds = (window.__fwAnimatedMsgs = window.__fwAnimatedMsgs || new Set());
  function StreamText({ id, text }) {
    const first = !animatedMsgIds.has(id);
    const [shown, setShown] = useState(first ? '' : text);
    useEffect(() => {
      if (!first) return;
      animatedMsgIds.add(id);
      const words = String(text).split(/(\s+)/);
      let i = 0;
      const timer = setInterval(() => {
        i += 1;
        setShown(words.slice(0, i).join(''));
        if (i >= words.length) clearInterval(timer);
      }, 26);
      return () => clearInterval(timer);
    }, []);
    return shown;
  }

  function AssistantMessage({ msg, ctx, isLast, busy }) {
    return (
      <div style={{ display: 'flex', gap: 12, alignItems: 'flex-start' }}>
        <Avatar />
        <div style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', gap: 12 }}>
          {msg.text && <div style={{ fontSize: 15, lineHeight: 1.6, color: 'var(--text)', paddingTop: 4, overflowWrap: 'anywhere', wordBreak: 'break-word' }}>{msg.fresh ? <StreamText id={msg.id} text={msg.text} /> : msg.text}</div>}
          {(msg.cards || []).map((card, i) => <CardRenderer key={i} card={card} ctx={ctx} />)}
          {isLast && !busy && (msg.suggestions || []).length > 0 && (
            <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, marginTop: 2 }}>
              {msg.suggestions.map((s, i) => (
                <button key={i} onClick={() => ctx.onSend(s)} style={{ padding: '7px 13px', borderRadius: 999, border: '1.5px solid var(--border)', background: 'var(--card)', color: 'var(--text2)', fontSize: 12.5, fontWeight: 600, cursor: 'pointer' }}>{s}</button>
              ))}
            </div>
          )}
        </div>
      </div>
    );
  }

  function UserMessage({ msg }) {
    return (
      <div style={{ display: 'flex', justifyContent: 'flex-end', minWidth: 0 }}>
        <div style={{ maxWidth: '80%', padding: '11px 16px', borderRadius: '18px 18px 4px 18px', background: 'var(--accent)', color: '#fff', fontSize: 14.5, lineHeight: 1.5, overflowWrap: 'anywhere', wordBreak: 'break-word' }}>{msg.text}</div>
      </div>
    );
  }

  function AssistantScreen({ cars, profile, addCar, onViewCar }) {
    const [messages, setMessages] = useState(() => {
      try { const s = JSON.parse(FW().safeGet('fw_chat_v1', 'null')); if (Array.isArray(s)) return s; } catch {}
      return [];
    });
    const [input, setInput] = useState('');
    const [busy, setBusy] = useState(false);
    const scrollRef = useRef(null);
    const taRef = useRef(null);
    const cancelRef = useRef(null);

    useEffect(() => { try { FW().safeSet('fw_chat_v1', JSON.stringify(messages.slice(-40))); } catch {} }, [messages]);
    useEffect(() => { const el = scrollRef.current; if (el) el.scrollTop = el.scrollHeight; }, [messages, busy]);

    const stop = () => { if (cancelRef.current) cancelRef.current.cancelled = true; setBusy(false); };

    const send = async (text) => {
      const t = (text || '').trim();
      if (!t || busy) return;
      const token = { cancelled: false };
      cancelRef.current = token;
      const userMsg = { id: Date.now(), role: 'user', text: t };
      const next = [...messages, userMsg];
      setMessages(next);
      setInput('');
      if (taRef.current) taRef.current.style.height = 'auto';
      setBusy(true);
      let res;
      try {
        res = await askClaude({ userText: t, history: next, cars, profile });
      } catch (e) {
        res = { message: "Something went wrong reaching the concierge. Try again in a moment.", cards: [], suggestions: [] };
      }
      if (token.cancelled) return;
      const resolved = [];
      for (const c of res.cards) { if (token.cancelled) return; const r = await resolveCard(c, { cars, profile }); if (r) resolved.push(r); }
      if (token.cancelled) return;
      setMessages(m => [...m, { id: Date.now() + 1, role: 'assistant', text: res.message, cards: resolved, suggestions: res.suggestions, fresh: true }]);
      setBusy(false);
    };

    const ctx = { cars, profile, addCar, onViewCar, onSend: send };
    const empty = messages.length === 0;
    const starters = buildStarters(profile, cars.length);

    return (
      <div style={{ height: 'calc(100vh - 60px)', display: 'flex', flexDirection: 'column', maxWidth: 760, margin: '0 auto', width: '100%' }}>
        <div ref={scrollRef} style={{ flex: 1, overflowY: 'auto', overflowX: 'hidden', padding: '28px 24px 12px', minWidth: 0 }}>
          {empty ? (
            <div style={{ minHeight: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', textAlign: 'center', padding: '20px 0' }}>
              <div style={{ width: 52, height: 52, borderRadius: 15, background: 'var(--accent)', display: 'flex', alignItems: 'center', justifyContent: 'center', marginBottom: 20 }}>
                <svg width="28" height="28" viewBox="0 0 24 24" fill="none"><path d="M4 14l2-6 8 2 7-6 3 4" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /><circle cx="8" cy="18" r="2" fill="#fff" /><circle cx="18" cy="18" r="2" fill="#fff" /></svg>
              </div>
              <h2 style={{ fontSize: 26, fontWeight: 800, letterSpacing: '-0.02em', marginBottom: 8 }}>Your car-buying concierge</h2>
              <p style={{ fontSize: 15, color: 'var(--text2)', maxWidth: 380, lineHeight: 1.6, marginBottom: 28 }}>Ask about any used car — I'll value it, flag what to inspect, compare your options, and help you make the call.</p>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 9, width: '100%', maxWidth: 420 }}>
                {starters.map((s, i) => (
                  <button key={i} onClick={() => send(s)} style={{ textAlign: 'left', padding: '13px 16px', borderRadius: 13, border: '1px solid var(--border)', background: 'var(--card)', color: 'var(--text)', fontSize: 14, fontWeight: 500, cursor: 'pointer', boxShadow: 'var(--shadow)' }}
                    onMouseEnter={e => e.currentTarget.style.borderColor = 'var(--accent)'}
                    onMouseLeave={e => e.currentTarget.style.borderColor = 'var(--border)'}>{s}</button>
                ))}
              </div>
            </div>
          ) : (
            <div style={{ display: 'flex', flexDirection: 'column', gap: 22, minWidth: 0 }}>
              {messages.map((m, i) => m.role === 'user'
                ? <UserMessage key={m.id} msg={m} />
                : <AssistantMessage key={m.id} msg={m} ctx={ctx} isLast={i === messages.length - 1} busy={busy} />)}
              {busy && <ThinkingBubble />}
            </div>
          )}
        </div>
        <div style={{ padding: '12px 24px 22px', borderTop: '1px solid var(--border)', background: 'var(--bg)' }}>
          <div style={{ display: 'flex', alignItems: 'flex-end', gap: 10, border: '1.5px solid var(--border)', borderRadius: 18, padding: '8px 8px 8px 16px', background: 'var(--card)' }}>
            <textarea ref={taRef} value={input} rows={1} placeholder="Ask about a car, a price, what to inspect…"
              aria-label="Message the concierge"
              onChange={e => { setInput(e.target.value); e.target.style.height = 'auto'; e.target.style.height = Math.min(e.target.scrollHeight, 140) + 'px'; }}
              onKeyDown={e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(input); } }}
              style={{ flex: 1, resize: 'none', border: 'none', outline: 'none', background: 'transparent', color: 'var(--text)', fontSize: 15, lineHeight: 1.5, fontFamily: 'inherit', maxHeight: 140, padding: '6px 0' }} />
            {busy
              ? <button onClick={stop} aria-label="Stop generating" title="Stop" style={{
                  width: 38, height: 38, borderRadius: 12, border: 'none', flexShrink: 0, cursor: 'pointer',
                  background: 'var(--text)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                  <svg width="15" height="15" viewBox="0 0 24 24"><rect x="5" y="5" width="14" height="14" rx="2.5" fill="var(--bg)" /></svg>
                </button>
              : <button onClick={() => send(input)} disabled={!input.trim()} aria-label="Send message" style={{
                  width: 38, height: 38, borderRadius: 12, border: 'none', flexShrink: 0, cursor: input.trim() ? 'pointer' : 'default',
                  background: input.trim() ? 'var(--accent)' : 'var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'center', transition: 'background 0.15s' }}>
                  <svg width="18" height="18" viewBox="0 0 24 24" fill="none"><path d="M12 19V5M5 12l7-7 7 7" stroke="#fff" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" /></svg>
                </button>}
          </div>
          <div style={{ textAlign: 'center', fontSize: 11, color: 'var(--text3)', marginTop: 8 }}>Concierge can make mistakes — verify pricing and history before you buy.</div>
        </div>
      </div>
    );
  }

  window.AssistantScreen = AssistantScreen;
})();
