// chat.jsx - VaultCova natural-language assistant: engine + UI (exported to window)
const { useState: useStateC, useEffect: useEffectC, useRef: useRefC } = React;

/* ============ NL parsing helpers ============ */
function parseAmount(str) {
  if (!str) return null;
  const s = str.toLowerCase().replace(/[,₦n]/g, " ");
  // 52m / 52 million / 1.2b / 52000000
  let m = s.match(/(\d+(?:\.\d+)?)\s*(b|bn|billion)/);
  if (m) return Math.round(parseFloat(m[1]) * 1e9);
  m = s.match(/(\d+(?:\.\d+)?)\s*(m|mn|million)/);
  if (m) return Math.round(parseFloat(m[1]) * 1e6);
  m = s.match(/(\d+(?:\.\d+)?)\s*(k|thousand)/);
  if (m) return Math.round(parseFloat(m[1]) * 1e3);
  m = s.match(/(\d{4,})/);
  if (m) return parseInt(m[1], 10);
  return null;
}
function detectCategory(s) {
  s = s.toLowerCase();
  if (/\b(car|truck|hilux|toyota|lexus|vehicle|van|bus|fleet|land cruiser|suv|motor)\b/.test(s)) return "vehicle";
  if (/\b(house|home|apartment|building|warehouse|property|shop|office|flat|duplex|land)\b/.test(s)) return "property";
  if (/\b(machine|equipment|generator|forklift|fridge|refriger|plant|tools|machinery)\b/.test(s)) return "equipment";
  if (/\b(laptop|macbook|phone|iphone|camera|tv|electronic|computer|console|gadget)\b/.test(s)) return "electronics";
  if (/\b(stock|inventory|goods|cargo|produce|merchandise)\b/.test(s)) return "inventory";
  if (/\b(watch|rolex|jewel|jewell|ring|gold|art|painting|valuable|collection|necklace)\b/.test(s)) return "jewelry";
  if (/\b(ship|vessel|boat|marine|shipment|container)\b/.test(s)) return "marine";
  return "equipment";
}
function cleanName(raw) {
  let n = " " + raw + " ";
  // 1. money amounts + units (longest-first so "million" isn't clipped to "illion")
  n = n.replace(/₦?\s*\d[\d,.]*\s*(billion|bn|million|mn|thousand|naira|b|m|k)?\b/gi, " ");
  // 2. value / price phrases
  n = n.replace(/\b(worth|valued? at|priced? at|replacement( cost)?( of)?|cost(ing)?( of)?|costs?)\b/gi, " ");
  // 3. cut trailing context clauses on strong connectors only (keeps "valued at" intact)
  n = n.replace(/\b(for|that|which|because|since|so that|used? for|that i|which i)\b.*$/i, " ");
  // 4. lead-in verbs / filler
  n = n.replace(/\b(add|register|i (just )?(bought|got|own|have|acquired|recently bought)|my|a|an|new|insure|please|to vault ?log|of|about|around)\b/gi, " ");
  // 5. tidy punctuation, stray hyphens, whitespace
  n = n.replace(/[^\w\s-]/g, " ").replace(/\s*-\s*/g, "-").replace(/^[\s-]+|[\s-]+$/g, "").replace(/\s+/g, " ").trim();
  if (!n) return "New asset";
  return n.split(" ").map((w) => w.length > 2 ? w[0].toUpperCase() + w.slice(1) : w).join(" ").slice(0, 40).trim();
}

/* ============ The engine ============ */
let _pendingAssess = null; // awaiting additional underwriting info

// returns { msgs:[{text, card, chips}], actions:[{type,...}] }
function interpret(text, ctx) {
  const t = text.toLowerCase().trim();
  const D = window.VC_DATA;
  const reply = (msgs, actions = []) => ({ msgs: Array.isArray(msgs) ? msgs : [msgs], actions });

  // ---- pending underwriting info answer ----
  if (_pendingAssess && !/\b(add|insure|claim|dashboard|vault|bundle|reward|download|covered)\b/.test(t)) {
    const pa = _pendingAssess;
    _pendingAssess = null;
    const improved = Math.max(18, pa.assess.score - 9);
    const newLevel = improved >= 66 ? "High" : improved >= 48 ? "Medium" : "Low";
    const est = Math.round(pa.asset.replacement * pa.assess.premiumRate * (newLevel === pa.assess.level ? 1 : 0.88) / 1000) * 1000;
    if (pa.assess.flag) {
      return reply([
        { text: `Noted - **“${text}”** added to the underwriting file.`, delay: 400 },
        { text: `Because of the value and risk profile, I've **flagged this to our underwriting team** for a final check. Most reviews complete within **4 business hours** - I'll notify you. Meanwhile, the indicative premium is **${D.fmtFull(est)}/yr**.`, card: { type: "assessment", assess: { ...pa.assess, score: improved, level: newLevel, factors: [...pa.assess.factors.slice(0, 2), "Your answer: " + text] }, asset: pa.asset, flagged: true }, chips: ["Open Cova Insight", "Add another asset"], delay: 900 },
      ], [{ type: "addFlag", flag: { kind: "underwriting", subject: pa.asset.name + " - " + D.fmtNaira(pa.asset.replacement), detail: "Cova assessment " + improved + "/100 (" + newLevel + "). User answered: \"" + text + "\"", risk: newLevel } }]);
    }
    return reply([
      { text: `Perfect - that improves your profile. Risk re-scored to **${improved}/100 (${newLevel})**.`, delay: 400 },
      { text: `I can bind cover at an estimated **${D.fmtFull(est)}/yr**. Want me to compare insurer quotes?`, card: { type: "assessment", assess: { ...pa.assess, score: improved, level: newLevel, factors: [...pa.assess.factors.slice(0, 2), "Your answer: " + text] }, asset: pa.asset }, chips: ["Compare quotes", "Not yet"], delay: 900 },
    ]);
  }

  // greeting / help
  if (/^(hi|hello|hey|good (morning|afternoon|evening)|what can you do|help|menu)/.test(t)) {
    return reply([{
      text: `I'm your VaultCova co-pilot. I can do all of this just from chat:`,
      chips: ["Add an asset to VaultLog", "Am I fully covered?", "Insure something", "Show my dashboard"],
    }]);
  }

  // add asset
  if (/\b(add|register|i (just )?(bought|got|acquired|own|have)|put .* (in|into) vault)/.test(t) || (/\b(worth|valued|replacement|cost)\b/.test(t) && parseAmount(t))) {
    const amount = parseAmount(t);
    const cat = detectCategory(t);
    const name = cleanName(text);
    const replacement = amount || Math.round((D.CATEGORIES[cat] ? 1 : 1) * 5000000);
    const asset = {
      id: D.nextId(), name, category: cat,
      purchase: Math.round(replacement * 0.86), replacement,
      sumInsured: 0, status: "uninsured", bundle: null, insurer: null, premium: 0, renews: null,
      note: "Added via chat",
    };
    const assess = D.assessRisk(asset);
    _pendingAssess = assess.infoNeeded.length ? { asset, assess } : null;
    const msgs = [
      { text: `Got it - adding **${name}** to your VaultLog.`, delay: 500 },
      {
        text: amount
          ? `Recorded at **${D.fmtFull(replacement)}** replacement. It's currently **uninsured** - here's my instant risk read:`
          : `Added. I've assumed a placeholder value - tell me the replacement cost anytime. Here's my instant risk read:`,
        card: { type: "asset", asset },
        delay: 850,
      },
      {
        text: assess.recommendation + (assess.infoNeeded.length ? ` One quick question:` : ``),
        card: { type: "assessment", assess, asset, flagged: assess.flag },
        chips: assess.infoNeeded.length ? assess.infoNeeded : ["Insure it now", "Bundle with others"],
        delay: 1300,
      },
    ];
    const actions = [{ type: "addAsset", asset, flash: true }];
    if (assess.flag) actions.push({ type: "addFlag", flag: { kind: "underwriting", subject: asset.name + " - " + D.fmtNaira(asset.replacement), detail: "Cova assessment " + assess.score + "/100 (" + assess.level + "). Awaiting user answers.", risk: assess.level } });
    return reply(msgs, actions);
  }

  // migrate existing policy (must run before coverage/insure - "policy" overlaps)
  if (/\b(already (have|insured|covered)|existing polic|move (my |a )?polic|transfer (my |a )?polic|migrate|bring my polic|manage my polic)/.test(t)) {
    return reply([
      { text: `Great - you don't need to start over. Tell me the insurer and policy number, and I'll **move it into VaultCova for management**: renewals, claims, rewards and gap analysis, with your cover untouched.`, delay: 500 },
      { text: `Opening the migration form now…`, delay: 800 },
    ], [{ type: "openMigrate" }]);
  }

  // coverage / gap
  if (/\b(covered|coverage|gap|underinsur|exposed|protect|enough cover|am i (fully )?(safe|protected))/.test(t)) {
    const p = D.portfolio(ctx.assets);
    return reply([{
      text: `Here's your live coverage picture across **${ctx.assets.length} assets**:`,
      card: { type: "coverage", p },
      chips: p.gap > 0 ? ["Close the gap", "Show uninsured assets", "Open VaultLog"] : ["Open VaultLog", "Get a renewal quote"],
    }]);
  }

  // insure / quotes — offers reflect the LIVE ops-managed underwriter panel
  if (/\b(insure|cover|quote|policy|policies|premium|protect it|get cover)/.test(t)) {
    const live = Array.isArray(ctx.panel) && ctx.panel.length
      ? ctx.panel.map((p2, i) => ({ insurer: p2.insurer, rating: p2.rating || 4.5, features: (p2.features || []).slice(0, 4), claimDays: 10 + (i % 4) * 2, premium: D.QUOTES[i % D.QUOTES.length].premium, badge: i === 0 ? "Best value" : null }))
      : D.QUOTES;
    return reply([
      { text: `I'll compare live quotes from our insurer panel. Reading your asset details…`, delay: 500 },
      { text: `Here ${live.length === 1 ? "is 1 matched offer" : `are ${live.length} matched offers`}. I've highlighted the best value:`, card: { type: "quotes", quotes: live }, delay: 1100 },
    ], [{ type: "navigateSoon", tab: "quotes" }]);
  }

  // bundle
  if (/\b(bundle|group|together|combine|portfolio cover)/.test(t)) {
    return reply([{
      text: `Bundling lowers your premium and removes overlap. I can group related assets into one policy:`,
      card: { type: "bundleSuggest" },
      chips: ["Create Fleet bundle", "Create Premises bundle", "Open Insure"],
    }], [{ type: "navigateSoon", tab: "insure" }]);
  }

  // claim
  if (/\b(claim|damaged|accident|stolen|loss|incident|file a)/.test(t)) {
    const insuredAssets = ctx.assets.filter((a) => a.insurer);
    // Did they name a specific asset? Match on any meaningful word of its name.
    const named = ctx.assets.find((a) => a.name.toLowerCase().split(/\s+/).some((w) => w.length > 2 && t.includes(w)));
    // No cover means no claim — say so plainly and offer to insure instead.
    if (named && !named.insurer) {
      return reply([{
        text: `**${named.name}** isn't insured, so there's no cover to claim against yet. The good news: I can get it protected in about a minute - want me to set up cover?`,
        chips: [`Insure ${named.name}`, "Am I fully covered?"],
      }]);
    }
    if (!insuredAssets.length) {
      return reply([{
        text: `You don't have any insured assets yet, so there's no cover to claim against. Let's fix that - I can find you cover in minutes.`,
        chips: ["Insure an asset", "Am I fully covered?"],
      }]);
    }
    return reply([{
      text: `Sorry to hear that. I'll triage this instantly - most claims under **₦5M** on active policies are **auto-approved for settlement**; larger or unusual ones go to a human adjuster. Which asset is affected?`,
      chips: insuredAssets.slice(0, 3).map((a) => a.name),
    }], [{ type: "navigateSoon", tab: "claims" }]);
  }

  // plans / subscription
  if (/\b(plans?|subscription|upgrade|pricing|see plans)\b/.test(t)) {
    return reply([{
      text: `Your policies, vault, claims and my guidance are **free forever**. The paid plans add business risk management on top - I'll show you in Settings.`,
    }], [{ type: "navigateSoon", tab: "settings" }]);
  }

  // risk profile / what do you know
  if (/\b(what do you know|my (risk )?profile|risk graph|knowledge graph|what have you learn|about me)\b/.test(t)) {
    const facts = Object.entries((ctx.graph || {}).facts || {});
    return reply([{
      text: facts.length
        ? `Here's what I've learned about you so far - always growing, never reset, and only used to protect you better:`
        : `I'm still learning about you. Just chat with me about what you own and do - I'll build your risk profile naturally.`,
      card: facts.length ? { type: "graph", graph: ctx.graph } : null,
      chips: ["What am I exposed to?", "Open Cova Insight"],
    }]);
  }

  // insight / risk overview — explain the score with the real engine, don't just navigate
  if (/\b(insight|risk (score|overview|profile)|how risky|my risk|recommendation|explain my (risk )?score)/.test(t)) {
    const ins = window.buildInsight ? window.buildInsight(ctx.assets, []) : null;
    if (ins && ctx.assets.length) {
      const f = ins.factors;
      const parts = [
        `**${f.base} pts** base - every portfolio starts here`,
        `**+${f.gapPoints} pts** coverage gap - ${Math.round(ins.p.covered * 100)}% of your ${D.fmtNaira(ins.p.replacement)} portfolio is insured (the gap is weighted heaviest)`,
      ];
      if (f.mixPoints) parts.push(`**+${f.mixPoints} pts** high-risk mix - ${f.highRiskCount} asset${f.highRiskCount > 1 ? "s" : ""} Cova treats as high-risk (marine, jewellery or ₦100M+)`);
      if (f.lapsingPoints) parts.push(`**+${f.lapsingPoints} pts** lapsing - ${f.lapsingCount} polic${f.lapsingCount > 1 ? "ies" : "y"} about to expire`);
      const top = ins.recs[0];
      return reply([{
        text: `Your risk score is **${ins.score}/100 (${ins.level})**. Here's exactly how I computed it:\n\n${parts.map((x) => "• " + x).join("\n")}\n\n${top ? `Fastest way to bring it down: **${top.title}** - ${top.detail}` : "Keep renewals on time to stay here."}`,
        chips: top ? [top.chat, "Open Cova Insight"] : ["Open Cova Insight"],
      }], [{ type: "navigateSoon", tab: "insight" }]);
    }
    return reply([{
      text: `**Cova Insight** scores your whole portfolio and tells you exactly what to fix first. Opening it now…`,
    }], [{ type: "navigate", tab: "insight" }]);
  }

  // rewards / cashback
  if (/\b(reward|cashback|cash back|premium back|loyalty|claim.?free|get money back|bonus)\b/.test(t)) {
    return reply([{
      text: `**VaultRewards** pays you back for staying claim-free, accrued **every month**:\n\n• **Bronze** from day one - up to **5%** of your annual premium\n• **Silver** after 2 claim-free years - up to **7%**\n• **Gold** after 5 claim-free years - up to **9%**\n\nA claim resets you to Bronze, but everything you've already accrued stays yours.`,
      chips: ["Open VaultRewards", "How is it calculated?", "Redeem my cashback"],
    }], [{ type: "navigateSoon", tab: "rewards" }]);
  }

  // documents / downloads
  if (/\b(download|certificate|schedule of asset|policy document|pdf|export|proof of cover)\b/.test(t)) {
    if (/schedule/.test(t)) {
      return reply([{ text: `Generating your **Schedule of Assets** - a full register with values and coverage. Opening it now…` }], [{ type: "downloadSchedule" }]);
    }
    return reply([{
      text: `I can generate official PDFs for you: a **Schedule of Assets** for your whole VaultLog, or a **Policy Certificate** for any insured asset or bundle.`,
      chips: ["Download schedule of assets", "Open VaultLog"],
    }]);
  }

  // dashboard / navigate
  if (/\b(dashboard|overview|home|summary)\b/.test(t)) return reply([{ text: "Opening your dashboard…" }], [{ type: "navigate", tab: "dashboard" }]);
  if (/\bvault ?log|my assets|asset register\b/.test(t)) return reply([{ text: "Here's your VaultLog." }], [{ type: "navigate", tab: "vaultlog" }]);

  // fallback
  return reply([{
    text: `I can help with that. Try one of these - or just describe what you own in plain words:`,
    chips: ["Add my car worth ₦52M", "Am I fully covered?", "Bundle my assets", "Start a claim"],
  }]);
}

/* ============ rich message cards ============ */
function MsgCard({ card, onAction }) {
  const D = window.VC_DATA;
  if (!card) return null;
  if (card.type === "asset") {
    const a = card.asset;
    return (
      <div style={{ marginTop: 10, padding: 13, borderRadius: 14, background: "var(--surface)", border: "1px solid var(--border)", display: "flex", alignItems: "center", gap: 12, animation: "vc-drop-in .6s var(--ease) both" }}>
        <window.AssetThumb category={a.category} size={46} />
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontWeight: 700, fontSize: 14 }}>{a.name}</div>
          <div className="mono" style={{ fontSize: 12.5, color: "var(--text-muted)" }}>{D.fmtFull(a.replacement)} replacement</div>
        </div>
        <window.StatusBadge status={a.status} size="sm" />
      </div>
    );
  }
  if (card.type === "coverage") {
    const p = card.p;
    return (
      <div style={{ marginTop: 10, padding: 16, borderRadius: 14, background: "var(--surface)", border: "1px solid var(--border)", display: "flex", gap: 16, alignItems: "center" }}>
        <window.CoverageRing pct={p.covered} size={92} stroke={10} sub="covered" />
        <div style={{ flex: 1, display: "grid", gap: 8 }}>
          <Row k="Total replacement value" v={D.fmtFull(p.replacement)} />
          <Row k="Currently insured" v={D.fmtFull(p.insured)} c="var(--success)" />
          <Row k="Coverage gap" v={D.fmtFull(p.gap)} c={p.gap ? "var(--danger)" : "var(--success)"} bold />
        </div>
      </div>
    );
  }
  if (card.type === "assessment") {
    const { assess, flagged } = card;
    const color = assess.level === "High" ? "var(--danger)" : assess.level === "Medium" ? "var(--warning)" : "var(--success)";
    return (
      <div style={{ marginTop: 10, borderRadius: 14, background: "var(--surface)", border: "1px solid var(--border)", overflow: "hidden", animation: "vc-fade-up .5s var(--ease) both" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 8, padding: "10px 14px", background: "var(--surface-2)", borderBottom: "1px solid var(--border)" }}>
          <window.Icon name="sparkle" size={15} style={{ color: "var(--accent)" }} />
          <span style={{ fontSize: 12, fontWeight: 700, color: "var(--text-muted)", letterSpacing: ".02em" }}>COVA RISK ASSESSMENT</span>
          {flagged && <span style={{ marginLeft: "auto", display: "inline-flex", alignItems: "center", gap: 5, fontSize: 10.5, fontWeight: 700, padding: "3px 9px", borderRadius: 999, background: "var(--warning-soft)", color: "var(--warning)" }}><window.Icon name="flag" size={11} />Admin review</span>}
        </div>
        <div style={{ display: "flex", gap: 14, padding: 14, alignItems: "center" }}>
          <div style={{ position: "relative", width: 62, height: 62, flexShrink: 0 }}>
            <svg width="62" height="62" style={{ transform: "rotate(-90deg)" }}>
              <circle cx="31" cy="31" r="26" fill="none" stroke="var(--surface-3)" strokeWidth="6" />
              <circle cx="31" cy="31" r="26" fill="none" stroke={color} strokeWidth="6" strokeLinecap="round" strokeDasharray={2 * Math.PI * 26} strokeDashoffset={2 * Math.PI * 26 * (1 - assess.score / 100)} />
            </svg>
            <div style={{ position: "absolute", inset: 0, display: "grid", placeItems: "center", textAlign: "center" }}>
              <div><div className="display mono" style={{ fontSize: 16, lineHeight: 1 }}>{assess.score}</div><div style={{ fontSize: 8.5, color: "var(--text-faint)", fontWeight: 700 }}>/100</div></div>
            </div>
          </div>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 13, fontWeight: 700, color }}>{assess.level} risk</div>
            <div style={{ display: "grid", gap: 4, marginTop: 6 }}>
              {assess.factors.map((f) => (
                <div key={f} style={{ display: "flex", alignItems: "center", gap: 6, fontSize: 12, color: "var(--text-muted)" }}>
                  <span style={{ width: 4, height: 4, borderRadius: 9, background: color, flexShrink: 0 }} />{f}
                </div>
              ))}
            </div>
          </div>
        </div>
      </div>
    );
  }
  if (card.type === "graph") {
    const D2 = window.VC_DATA;
    const facts = Object.entries(card.graph.facts).sort((a, b) => b[1].conf - a[1].conf);
    const dims = {};
    facts.forEach(([k, v]) => { const d = D2.RISK_FACTS[k]?.dim || "Other"; (dims[d] = dims[d] || []).push([k, v]); });
    return (
      <div style={{ marginTop: 10, borderRadius: 14, background: "var(--surface)", border: "1px solid var(--border)", overflow: "hidden" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 8, padding: "10px 14px", background: "var(--surface-2)", borderBottom: "1px solid var(--border)" }}>
          <window.Icon name="eye" size={15} style={{ color: "var(--accent)" }} />
          <span style={{ fontSize: 12, fontWeight: 700, color: "var(--text-muted)", letterSpacing: ".02em" }}>YOUR RISK PROFILE</span>
        </div>
        <div style={{ padding: 14, display: "grid", gap: 12 }}>
          {Object.entries(dims).map(([dim, items]) => (
            <div key={dim}>
              <div style={{ fontSize: 10.5, fontWeight: 700, color: "var(--text-faint)", textTransform: "uppercase", letterSpacing: ".04em", marginBottom: 6 }}>{dim}</div>
              <div style={{ display: "grid", gap: 6 }}>
                {items.map(([k, v]) => (
                  <div key={k} style={{ display: "flex", alignItems: "center", gap: 9 }}>
                    <span style={{ fontSize: 12.5, fontWeight: 600, flex: 1 }}>{D2.RISK_FACTS[k]?.label || k}{v.value ? <span style={{ color: "var(--text-faint)" }}> · {v.value}</span> : null}</span>
                    <div style={{ width: 70, height: 6, borderRadius: 999, background: "var(--surface-3)", overflow: "hidden" }}>
                      <div style={{ height: "100%", width: (v.conf * 100) + "%", background: v.conf >= 0.9 ? "var(--success)" : "var(--accent)", borderRadius: 999 }} />
                    </div>
                    <span className="mono" style={{ fontSize: 11, fontWeight: 700, color: "var(--text-muted)", width: 34, textAlign: "right" }}>{Math.round(v.conf * 100)}%</span>
                  </div>
                ))}
              </div>
            </div>
          ))}
        </div>
      </div>
    );
  }
  if (card.type === "crosssell") {
    const rec = card.rec;
    return (
      <div style={{ marginTop: 10, borderRadius: 14, background: "var(--surface)", border: "1px solid var(--accent)", overflow: "hidden", animation: "vc-fade-up .5s var(--ease) both" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 8, padding: "10px 14px", background: "var(--accent-soft)" }}>
          <window.Icon name="shield" size={15} style={{ color: "var(--accent)" }} />
          <span style={{ fontSize: 12, fontWeight: 700, color: "var(--accent-ink)", letterSpacing: ".02em" }}>PROTECTION GAP SPOTTED</span>
          <span className="mono" style={{ marginLeft: "auto", fontSize: 10.5, fontWeight: 700, color: "var(--accent-ink)" }}>{Math.round(rec.conf * 100)}% confident</span>
        </div>
        <div style={{ padding: 14 }}>
          <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
            {rec.products.map((p) => <span key={p} style={{ fontSize: 11.5, fontWeight: 700, padding: "4px 11px", borderRadius: 999, background: "var(--accent-soft)", color: "var(--accent-ink)" }}>{p}</span>)}
          </div>
        </div>
      </div>
    );
  }
  if (card.type === "quotes") {
    return (
      <div style={{ marginTop: 10, display: "grid", gap: 8 }}>
        {card.quotes.map((q, i) => (
          <div key={q.insurer} style={{ padding: 12, borderRadius: 13, background: "var(--surface)", border: "1px solid " + (q.badge === "Best value" ? "var(--accent)" : "var(--border)"), display: "flex", alignItems: "center", gap: 11, animation: `vc-fade-up .4s var(--ease) ${i * 0.08}s both` }}>
            <div style={{ flex: 1 }}>
              <div style={{ fontWeight: 700, fontSize: 13.5, display: "flex", alignItems: "center", gap: 7 }}>{q.insurer}
                {q.badge && <span style={{ fontSize: 10, fontWeight: 700, padding: "2px 7px", borderRadius: 999, background: "var(--accent-soft)", color: "var(--accent-ink)" }}>{q.badge}</span>}
              </div>
              <div className="mono" style={{ fontSize: 16, fontWeight: 700, marginTop: 2 }}>{D.fmtFull(q.premium)}<span style={{ fontSize: 11, color: "var(--text-faint)", fontWeight: 500 }}>/yr</span></div>
            </div>
            <window.Btn size="sm" onClick={() => onAction({ type: "selectQuote", quote: q })}>Select</window.Btn>
          </div>
        ))}
      </div>
    );
  }
  if (card.type === "bundleSuggest") {
    return (
      <div style={{ marginTop: 10, padding: 14, borderRadius: 14, background: "var(--accent-soft)", border: "1px solid var(--accent)", display: "flex", gap: 12, alignItems: "center" }}>
        <span style={{ display: "grid", placeItems: "center", width: 40, height: 40, borderRadius: 12, background: "var(--accent)", color: "#fff" }}><window.Icon name="layers" size={22} /></span>
        <div style={{ fontSize: 13, color: "var(--accent-ink)", fontWeight: 600 }}>Bundling 3 assets into one policy could save you ~<b>18%</b> on premium.</div>
      </div>
    );
  }
  return null;
}
function Row({ k, v, c, bold }) {
  return (
    <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", fontSize: 13 }}>
      <span style={{ color: "var(--text-muted)" }}>{k}</span>
      <span className="mono" style={{ fontWeight: bold ? 700 : 600, color: c || "var(--text)" }}>{v}</span>
    </div>
  );
}

/* ============ typing dots ============ */
function Typing() {
  return (
    <div style={{ display: "flex", gap: 5, padding: "12px 16px" }}>
      {[0, 1, 2].map((i) => <span key={i} style={{ width: 7, height: 7, borderRadius: 999, background: "var(--text-faint)", animation: `vc-dot 1.2s ${i * 0.15}s infinite` }} />)}
    </div>
  );
}

/* ============ Chat panel ============ */
// style: 'glass' | 'bubbles' | 'minimal'
function ChatPanel({ messages, onSend, onAction, busy, chatStyle = "glass", placeholder = "Describe what you own, or ask anything…", compact = false, onImport = null }) {
  // File import: the paperclip opens a picker; the "Import from a file" chip
  // dispatches vc-open-import so it lands on the same picker.
  const fileRef = React.useRef(null);
  React.useEffect(() => {
    if (!onImport) return;
    const open = () => fileRef.current && fileRef.current.click();
    window.addEventListener("vc-open-import", open);
    return () => window.removeEventListener("vc-open-import", open);
  }, [onImport]);
  const pickFile = (e) => {
    const f = e.target.files && e.target.files[0];
    e.target.value = ""; // allow re-picking the same file
    if (f && onImport) onImport(f);
  };
  const [input, setInput] = useStateC("");
  const scrollRef = useRefC(null);
  const { t, lang } = window.useT();
  const locale = window.I18N.localeFor(lang);
  useEffectC(() => { if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight; }, [messages, busy]);

  // ---- voice: mode-driven (off | dictate | autosend | conversation) ----
  // off       - no mic (type only)
  // dictate   - mic transcribes into the box; you review & send
  // autosend  - mic transcribes and sends automatically on completion
  // conversation - hands-free: autosend + Cova speaks replies + mic reopens for back-and-forth
  const voiceMode = window.__vcVoiceMode || "autosend";
  const modeRef = useRefC(voiceMode); modeRef.current = voiceMode;
  const voiceRef = useRefC(null);

  const voice = window.VC_VOICE.useVoiceInput({
    locale,
    onInterim: (txt) => setInput(txt),
    onResult: (txt) => {
      if (!txt || !txt.trim()) return;
      if (modeRef.current === "dictate") { setInput(txt.trim()); return; }
      setInput("");
      window.VC_VOICE.cancelSpeak();
      onSend(txt.trim());
    },
  });
  voiceRef.current = voice;

  // stop listening / speaking cleanly when the mode changes (e.g. user switches away)
  useEffectC(() => {
    if (voiceMode === "off" && voice.listening) voice.stop();
    if (voiceMode !== "conversation") window.VC_VOICE.cancelSpeak();
  }, [voiceMode]);

  // silence Cova if the panel unmounts mid-sentence
  useEffectC(() => () => window.VC_VOICE.cancelSpeak(), []);

  // ---- voice replies (text-to-speech) - conversation mode reads each new Cova reply,
  //      then reopens the mic so the user can answer hands-free ----
  const lastSpoke = useRefC(messages.length);
  useEffectC(() => {
    if (modeRef.current !== "conversation") { lastSpoke.current = messages.length; return; }
    let spokeOne = false;
    for (let i = lastSpoke.current; i < messages.length; i++) {
      const m = messages[i];
      if (m && m.role === "assistant" && m.text && !spokeOne) {
        spokeOne = true;
        window.VC_VOICE.speak(m.text, locale, () => {
          // reopen mic for the next turn, if still in conversation mode and idle
          if (modeRef.current === "conversation" && voiceRef.current && !voiceRef.current.listening) {
            setTimeout(() => { if (modeRef.current === "conversation") voiceRef.current.start(); }, 250);
          }
        });
      } else if (m && m.role === "assistant" && m.text) {
        window.VC_VOICE.speak(m.text, locale);
      }
    }
    lastSpoke.current = messages.length;
  }, [messages, locale]);

  const submit = (e) => { e && e.preventDefault(); if (!input.trim()) return; window.VC_VOICE.cancelSpeak(); onSend(input.trim()); setInput(""); };

  const isGlass = chatStyle === "glass";
  const isMinimal = chatStyle === "minimal";
  const DEFAULT_PH = ["Describe what you own, or ask anything…", "Tell Cova anything…"];
  const ph = voice.listening ? t("chat.listening") : (DEFAULT_PH.includes(placeholder) ? t(compact ? "dock.placeholder" : "chat.placeholder") : placeholder);

  return (
    <div style={{ display: "flex", flexDirection: "column", height: "100%", minHeight: 0, minWidth: 0 }}>
      <div ref={scrollRef} style={{ flex: 1, overflowY: "auto", padding: compact ? "16px 16px 8px" : "26px 26px 10px", display: "flex", flexDirection: "column", gap: 14 }}>
        {messages.map((m, i) => (
          <Bubble key={i} m={m} onAction={onAction} onSend={onSend} chatStyle={chatStyle} />
        ))}
        {busy && (
          <div style={{ alignSelf: "flex-start", borderRadius: 16, background: isGlass ? "var(--glass)" : "var(--surface-3)", border: isGlass ? "1px solid var(--glass-border)" : "none", backdropFilter: isGlass ? "blur(10px)" : "none" }}><Typing /></div>
        )}
      </div>
      <form onSubmit={submit} style={{ padding: compact ? 14 : "16px 26px 24px", flexShrink: 0 }}>
        <div style={{
          display: "flex", alignItems: "center", gap: 10, padding: "8px 8px 8px 18px", borderRadius: 999,
          background: isGlass ? "var(--glass)" : "var(--surface)", border: "1px solid " + (isMinimal ? "var(--border-strong)" : "var(--border)"),
          backdropFilter: isGlass ? "blur(14px) saturate(160%)" : "none", boxShadow: "var(--shadow-md)",
        }}>
          <window.Icon name="sparkle" size={18} style={{ color: "var(--accent)", flexShrink: 0 }} />
          <input value={input} onChange={(e) => setInput(e.target.value)} placeholder={ph}
            style={{ flex: 1, minWidth: 0, border: "none", outline: "none", background: "transparent", fontSize: 15, color: "var(--text)" }} />
          {onImport && (
            <>
              <input ref={fileRef} type="file" accept=".pdf,.csv,.txt,.xlsx,.xls,.docx,.doc,application/pdf,text/csv" style={{ display: "none" }} onChange={pickFile} />
              <button type="button" onClick={() => fileRef.current && fileRef.current.click()} disabled={busy} title="Import assets from a PDF, Excel, Word or CSV file"
                style={{ display: "grid", placeItems: "center", width: 38, height: 38, borderRadius: 999, color: "var(--text-muted)", opacity: busy ? 0.4 : 1, transition: "color .2s" }}
                onMouseEnter={(e) => e.currentTarget.style.color = "var(--accent)"} onMouseLeave={(e) => e.currentTarget.style.color = "var(--text-muted)"}>
                <window.Icon name="clip" size={19} />
              </button>
            </>
          )}
          {voiceMode !== "off" && (
          <button type="button" onClick={voice.toggle} title={voice.supported ? (voice.listening ? t("chat.listening") : (voiceMode === "conversation" ? "Conversation" : "Voice")) : t("chat.voiceUnsupported")} style={{ display: "grid", placeItems: "center", width: 38, height: 38, borderRadius: 999, color: voice.listening ? "#fff" : (voiceMode === "conversation" ? "var(--accent)" : "var(--text-muted)"), background: voice.listening ? "var(--danger)" : "transparent", opacity: voice.supported ? 1 : 0.45, transition: "all .2s var(--ease)", animation: voice.listening ? "vc-pulse-ring 1.6s infinite" : "none" }}>
            <window.Icon name="mic" size={19} />
          </button>
          )}
          <button type="submit" style={{ display: "grid", placeItems: "center", width: 42, height: 42, borderRadius: 999, background: "var(--accent)", color: "#fff", boxShadow: "var(--shadow-glow)", transition: "transform .2s" }}
            onMouseDown={(e) => e.currentTarget.style.transform = "scale(.92)"} onMouseUp={(e) => e.currentTarget.style.transform = "none"}>
            <window.Icon name="send" size={19} />
          </button>
        </div>
      </form>
    </div>
  );
}

function Bubble({ m, onAction, onSend, chatStyle }) {
  const isUser = m.role === "user";
  const isGlass = chatStyle === "glass";
  const renderText = (txt) => txt.split(/(\*\*[^*]+\*\*)/).map((p, i) =>
    p.startsWith("**") ? <b key={i}>{p.slice(2, -2)}</b> : <span key={i}>{p}</span>);
  return (
    <div style={{ alignSelf: isUser ? "flex-end" : "flex-start", maxWidth: "86%", animation: "vc-fade-up .4s var(--ease) both" }}>
      {!isUser && (
        <div style={{ display: "flex", alignItems: "center", gap: 7, marginBottom: 5, paddingLeft: 4 }}>
          <span style={{ display: "grid", placeItems: "center", width: 22, height: 22, borderRadius: 999, background: "linear-gradient(135deg,var(--accent-2),var(--accent))", color: "#fff" }}><window.Icon name="sparkle" size={13} /></span>
          <span style={{ fontSize: 12, fontWeight: 700, color: "var(--text-muted)" }}>Cova</span>
        </div>
      )}
      {m.text && (
        <div style={{
          padding: "12px 16px", borderRadius: isUser ? "18px 18px 6px 18px" : "6px 18px 18px 18px", fontSize: 14.5, lineHeight: 1.5,
          background: isUser ? "var(--accent)" : (isGlass ? "var(--glass)" : "var(--surface)"),
          color: isUser ? "var(--on-accent)" : "var(--text)",
          border: isUser ? "none" : "1px solid " + (isGlass ? "var(--glass-border)" : "var(--border)"),
          backdropFilter: !isUser && isGlass ? "blur(12px) saturate(150%)" : "none",
          boxShadow: isUser ? "var(--shadow-glow)" : "var(--shadow-sm)",
        }}>{renderText(m.text)}</div>
      )}
      {m.card && <MsgCard card={m.card} onAction={onAction} />}
      {m.chips && (
        <div style={{ display: "flex", flexWrap: "wrap", gap: 8, marginTop: 10 }}>
          {m.chips.map((c) => (
            <button key={c} onClick={() => onSend(c)} style={{
              padding: "7px 14px", borderRadius: 999, fontSize: 13, fontWeight: 600, color: "var(--accent-ink)",
              background: "var(--surface)", border: "1px solid var(--accent)", transition: "all .2s var(--ease)",
            }} onMouseEnter={(e) => { e.currentTarget.style.background = "var(--accent-soft)"; }} onMouseLeave={(e) => { e.currentTarget.style.background = "var(--surface)"; }}>{c}</button>
          ))}
        </div>
      )}
    </div>
  );
}

Object.assign(window, { interpret, ChatPanel, parseAmount });
