/* JARVIS v2 — wired to /api/jarvis-action (Claude Sonnet 4.6 + tool use)
 * Fallback: local intent router on PRODUCTS / SUPPLIERS / REVENUE_MONTHLY when API unavailable
 * Persists thread to localStorage. Streams assistant text. Handles tool calls.
 */

const JARVIS_STORAGE_KEY = "kcc_jarvis_thread_v2";

const SUGGESTED_PROMPTS = [
  // Analyses
  { icon: "search",  label: "Analyse top produits",  text: "Analyse mes meilleurs produits." },
  { icon: "alert",   label: "Alertes critiques",     text: "Alertes critiques à traiter." },
  { icon: "dollar",  label: "Mes finances",          text: "Montre-moi mes finances et le profit du trimestre." },
  // Actions autonomes (v3)
  { icon: "image",   label: "Trouver les images",    text: "Trouve une image pour chaque produit du catalogue qui n'en a pas." },
  { icon: "users",   label: "Ajouter contact",       text: "Ajoute le contact +86 138 0013 8000 au fournisseur principal en commercial." },
  { icon: "sparkles",label: "Tour du site",          text: "Fais un tour complet du site et signale-moi tout problème détecté." },
  // Communication
  { icon: "bell",    label: "Notifier l'équipe",     text: "Notifie l'équipe d'une alerte critique : rupture imminente sur les top sellers." },
  { icon: "receipt", label: "Dépenses récap",        text: "Récapitule mes dépenses annuelles par catégorie (T2125/TP-80)." },
  { icon: "mail",    label: "Message équipe",        text: "Envoie un message à Kevin et Carly : 'à voir les ruptures urgentes /go/research'" },
];

// ─────────────────────────────────────────────────────────────
// Local intent router — 20+ handlers, scoring-based intent match
// ─────────────────────────────────────────────────────────────
const $ = (n) => "$" + (n || 0).toLocaleString();
const trim = (s, n = 5) => String(s || "").split(" ").slice(0, n).join(" ");

function localAnswer(prompt) {
  const q = prompt.toLowerCase();
  const P = window.PRODUCTS || [];
  const S = window.SUPPLIERS || [];
  const R = window.REVENUE_MONTHLY || [];
  const A = window.ALERTS || [];

  // ── ASIN lookup (highest priority — explicit identifier)
  const asinMatch = prompt.match(/B0[A-Z0-9]{8}/);
  if (asinMatch) {
    const p = P.find(x => x.asin === asinMatch[0]);
    if (p) {
      const cov = p.vel > 0 ? `couvre ${(p.stock / p.vel).toFixed(1)}j` : "vel=0";
      return {
        text: `**${p.name}** \`${p.asin}\``,
        blocks: [{ kind: "list", items: [
          `Prix ${$(p.price)} · Coût ${$(p.cost)} · Frais ${$(p.fees)} · Marge **${p.margin.toFixed(1)}%**`,
          `Stock ${p.stock}u · Velocité ${p.vel}/j · ${cov}`,
          `BSR ${p.bsr.toLocaleString()} · Catégorie ${p.cat}`,
          `Fournisseur **${p.supplier}** · Owner ${p.owner} · Statut ${p.status}`,
        ]}],
      };
    }
    return { text: `ASIN \`${asinMatch[0]}\` introuvable au catalogue.` };
  }

  // ── Help / capabilities
  if (/que peux.tu|capacit|what can|help|aide|comment/.test(q) && q.length < 60) {
    return {
      text: "Voici ce que je peux faire en mode Local (sans API, gratuit) :",
      blocks: [{ kind: "list", items: [
        "**Top marges** — meilleurs/pires produits par marge",
        "**Ruptures** — produits avec couverture < 14j",
        "**Opportunités** — bonne marge mais BSR élevé",
        "**ACOS / PPC** — frais qui rongent la marge",
        "**Stock mort** — velocité 0 mais inventaire > 0",
        "**Cash & P&L** — projection, trésorerie, comparatif mensuel",
        "**Restock reco** — quantités à recommander",
        "**Fournisseurs** — top, lents, MOQ élevés, comptes ouverts",
        "**Catégories** — répartition CA, marges",
        "**Owner** — workload Carly / Kevin / Clarens",
        "**Inventory value** — valeur d'inventaire totale",
        "**Action plan** — 3 actions prioritaires cette semaine",
        "**Lookup ASIN** — tape B0XXXXXXXX",
        "**Navigation** — \"ouvre landed cost / finances / etc.\"",
      ]}],
    };
  }

  // ── Navigation
  const navMap = {
    "landed cost": "landed-cost", landed: "landed-cost",
    "asin library": "asin", bibliothèque: "asin",
    dashboard: "dashboard", finances: "finance", finance: "finance",
    opportunity: "opportunity", opportunit: "opportunity",
    alerte: "alerts", team: "team-board", teamboard: "team-board",
    calendrier: "calendar", fournisseur: "supplier", annuaire: "supplier",
    catalog: "catalogs", "compte ouvert": "open-acct",
    expédition: "shipments", retour: "returns", prep: "prep",
    profitcalc: "profit-calc", ressource: "resources", "atelier sql": "workshop",
    // Nouvelles pages
    dépense: "expenses", depense: "expenses", expense: "expenses", impôt: "expenses", impot: "expenses",
    facture: "expenses", receipt: "expenses", tps: "expenses", tvq: "expenses",
    message: "messages", chat: "messages", équipe: "messages", equipe: "messages",
  };
  if (/^(ouvre|va|naviga|aller|montre|page)/i.test(prompt) || q.startsWith("/")) {
    for (const [k, v] of Object.entries(navMap)) {
      if (q.includes(k)) {
        if (window.kccNavigate) window.kccNavigate(v);
        return { text: `Navigation → **${k}** ✓`, toolCalls: [{ name: "navigate", input: { page: v } }] };
      }
    }
  }

  // ── Top margins
  if (/(top|meilleure?|haute?s?|élevée?s?).*marge|marge.*(top|élevée?)|5 produits.*marge/.test(q)) {
    const top = [...P].sort((a, b) => b.margin - a.margin).slice(0, 5);
    return {
      text: "5 produits avec la marge la plus élevée :",
      blocks: [{ kind: "list", items: top.map(p => `**${trim(p.name)}** \`${p.asin}\` — marge **${p.margin.toFixed(1)}%** · stock ${p.stock}u · BSR ${p.bsr.toLocaleString()}`) }],
    };
  }

  // ── Worst margins / underperformers
  if (/(pire|faible|basse|low).*marge|marge.*(pire|faible|basse)|sous-performant|underperform/.test(q)) {
    const bottom = [...P].filter(p => p.status === "stock").sort((a, b) => a.margin - b.margin).slice(0, 5);
    return {
      text: "5 produits avec la marge la plus faible (encore en vente) :",
      blocks: [{ kind: "list", items: bottom.map(p => `**${trim(p.name)}** \`${p.asin}\` — marge **${p.margin.toFixed(1)}%** · prix ${$(p.price)} · vel ${p.vel}/j`) }],
    };
  }

  // ── Stock risk / restock
  if (/rupture|stock critique|stock.*bas|14j|couverture|restock|recommand/.test(q)) {
    const risk = P.filter(p => p.vel > 0 && p.stock > 0 && (p.stock / p.vel) < 14)
      .sort((a, b) => (a.stock / a.vel) - (b.stock / b.vel));
    if (!risk.length) return { text: "Aucun produit en zone rupture (<14j de couverture)." };
    return {
      text: `**${risk.length} produit(s) à réapprovisionner** (couverture <14j) :`,
      blocks: [
        { kind: "list", items: risk.slice(0, 8).map(p => {
          const days = p.stock / p.vel;
          const order = Math.max(0, Math.round(p.vel * 30 - p.stock));
          return `**${trim(p.name)}** \`${p.asin}\` — ${p.stock}u · couvre **${days.toFixed(1)}j** → recommander **${order}u** (30j)`;
        }) },
        { kind: "note", text: `Total à commander: ${risk.reduce((a, p) => a + Math.max(0, Math.round(p.vel * 30 - p.stock)), 0)}u pour 30j de couverture.` }
      ],
    };
  }

  // ── Dead stock
  if (/stock mort|dead stock|velocité.*0|invendu|vel ?= ?0/.test(q)) {
    const dead = P.filter(p => p.vel === 0 && p.stock > 0);
    if (!dead.length) return { text: "Aucun stock mort détecté ✓ tout bouge." };
    const lostCash = dead.reduce((a, p) => a + p.cost * p.stock, 0);
    return {
      text: `**${dead.length} produit(s) en stock mort** — capital immobilisé **${$(Math.round(lostCash))}**`,
      blocks: [{ kind: "list", items: dead.map(p => `**${trim(p.name)}** \`${p.asin}\` — ${p.stock}u × ${$(p.cost)} = ${$(p.stock * p.cost)} bloqués`) }],
    };
  }

  // ── ACOS / PPC / fees eating margin
  if (/acos|ppc|frais.*ronge|fees.*marge|pub|publicité/.test(q)) {
    const heavy = P.filter(p => p.fees / p.price > 0.3).sort((a, b) => (b.fees / b.price) - (a.fees / a.price)).slice(0, 5);
    if (!heavy.length) return { text: "Tous tes produits ont des frais Amazon raisonnables (<30% du prix) ✓" };
    return {
      text: "Produits avec frais Amazon > 30% du prix :",
      blocks: [{ kind: "list", items: heavy.map(p => `**${trim(p.name)}** — frais ${$(p.fees)} sur ${$(p.price)} (**${(p.fees / p.price * 100).toFixed(0)}%**) · marge ${p.margin.toFixed(1)}%`) }],
    };
  }

  // ── Opportunities
  if (/opportunit|sous-exploité|potentiel|push/.test(q)) {
    const opps = P.filter(p => p.margin >= 27 && p.bsr > 3000 && p.vel < 10).slice(0, 5);
    return {
      text: "Produits sous-exploités (marge ≥27% · BSR >3000 · velocité <10/j) :",
      blocks: [{ kind: "list", items: opps.map(p => `**${trim(p.name)}** \`${p.asin}\` — marge ${p.margin}% · BSR ${p.bsr.toLocaleString()} · ${p.vel}/j — push PPC ?`) }],
    };
  }

  // ── P&L last N months
  if (/pnl|p&l|profit.*mois|revenu.*mois|3 derniers? mois|6 derniers? mois|année|trim/.test(q)) {
    const n = /6 derniers/.test(q) ? 6 : /année|12/.test(q) ? 12 : /trim/.test(q) ? 3 : 3;
    const last = R.slice(-n);
    const totalRev = last.reduce((a, x) => a + x.rev, 0);
    const totalP = last.reduce((a, x) => a + x.profit, 0);
    const trend = last[last.length - 1].profit > last[0].profit ? "↑" : "↓";
    return {
      text: `**${n} derniers mois** — revenu ${$(totalRev)} · profit ${$(totalP)} (${(totalP / totalRev * 100).toFixed(1)}% marge nette) ${trend}`,
      blocks: [
        { kind: "list", items: last.map(m => `**${m.m}** · ${$(m.rev)} → profit ${$(m.profit)} (${(m.profit / m.rev * 100).toFixed(1)}%)`) },
        { kind: "note", text: `Variation profit ${last[0].m} → ${last[last.length-1].m}: ${((last[last.length-1].profit - last[0].profit) / last[0].profit * 100).toFixed(1)}%` }
      ],
    };
  }

  // ── Cash projection
  if (/trésorerie|cash|projection|prévision/.test(q)) {
    const lastProfit = R[R.length - 1]?.profit || 0;
    const avg3 = R.slice(-3).reduce((a, x) => a + x.profit, 0) / 3;
    const proj3 = Math.round(avg3 * 3);
    return {
      text: `**Projection cash sur 3 mois**`,
      blocks: [{ kind: "list", items: [
        `Profit dernier mois (${R[R.length-1]?.m}) : **${$(lastProfit)}**`,
        `Moyenne 3 derniers mois : ${$(Math.round(avg3))}/mois`,
        `Projection 3 mois (constant) : **${$(proj3)}**`,
        `Hypothèse +10% croissance: ${$(Math.round(proj3 * 1.1))}`,
      ]}],
    };
  }

  // ── Best supplier
  if (/fournisseur.*top|meilleur.*fournisseur|ratio.*fournisseur|fournisseur.*top|classement.*fournisseur/.test(q)) {
    const ranked = [...S].map(s => ({ ...s, val: s.score - (s.leadDays * 0.4) - (s.moq * 0.005) }))
      .sort((a, b) => b.val - a.val).slice(0, 3);
    return {
      text: "Top 3 fournisseurs (score − pénalité délai/MOQ) :",
      blocks: [{ kind: "list", items: ranked.map(s => `**${s.name}** — score ${s.score} · délai ${s.leadDays}j · MOQ ${s.moq}u · ${s.terms}`) }],
    };
  }

  // ── Slow suppliers
  if (/fournisseur.*lent|fournisseur.*retard|délai.*long|lead.*long/.test(q)) {
    const slow = [...S].sort((a, b) => b.leadDays - a.leadDays).slice(0, 3);
    return {
      text: "3 fournisseurs avec les plus longs délais :",
      blocks: [{ kind: "list", items: slow.map(s => `**${s.name}** — ${s.leadDays}j · ${s.cat} · ${s.terms}`) }],
    };
  }

  // ── Open balances / payments due
  if (/compte ouvert|solde|balance|échéance|paiement|à payer/.test(q)) {
    const open = S.filter(s => s.openBalance > 0).sort((a, b) => b.openBalance - a.openBalance);
    const total = open.reduce((a, s) => a + s.openBalance, 0);
    return {
      text: `**${open.length} comptes ouverts** · total dû **${$(total)}**`,
      blocks: [{ kind: "list", items: open.map(s => `**${s.name}** — ${$(s.openBalance)} · ${s.terms}`) }],
    };
  }

  // ── Inventory value
  if (/valeur.*inventaire|valeur.*stock|capital.*stock|inventory value/.test(q)) {
    const inStock = P.filter(p => p.stock > 0);
    const totalCost = inStock.reduce((a, p) => a + p.cost * p.stock, 0);
    const totalSale = inStock.reduce((a, p) => a + p.price * p.stock, 0);
    return {
      text: `**Valeur d'inventaire**`,
      blocks: [{ kind: "list", items: [
        `Au coût : **${$(Math.round(totalCost))}** (${inStock.length} SKUs)`,
        `Au prix de vente : ${$(Math.round(totalSale))}`,
        `Marge brute potentielle : ${$(Math.round(totalSale - totalCost))} (${((totalSale - totalCost) / totalSale * 100).toFixed(0)}%)`,
      ]}],
    };
  }

  // ── Velocity top sellers
  if (/best.?seller|plus.*vendu|haute? velocité|top vente|rapide.*vente/.test(q)) {
    const top = [...P].sort((a, b) => b.vel - a.vel).slice(0, 5);
    return {
      text: "Top 5 par velocité de vente :",
      blocks: [{ kind: "list", items: top.map(p => `**${trim(p.name)}** \`${p.asin}\` — **${p.vel}/j** · stock ${p.stock}u · marge ${p.margin}%`) }],
    };
  }

  // ── Category breakdown
  if (/catégorie|categorie|category|répartition/.test(q)) {
    const byCat = {};
    P.forEach(p => {
      if (!byCat[p.cat]) byCat[p.cat] = { count: 0, rev: 0, margin: 0 };
      byCat[p.cat].count++;
      byCat[p.cat].rev += p.price * p.vel * 30;
      byCat[p.cat].margin += p.margin;
    });
    const sorted = Object.entries(byCat).map(([k, v]) => ({ cat: k, ...v, avgMargin: v.margin / v.count }))
      .sort((a, b) => b.rev - a.rev);
    return {
      text: `Répartition par catégorie (${Object.keys(byCat).length}) :`,
      blocks: [{ kind: "list", items: sorted.map(c => `**${c.cat}** — ${c.count} SKUs · CA proj 30j ${$(Math.round(c.rev))} · marge moy ${c.avgMargin.toFixed(1)}%`) }],
    };
  }

  // ── Owner workload
  if (/owner|carly|kevin|clarens|charge.*équipe|workload/.test(q)) {
    const byOwner = {};
    P.forEach(p => {
      if (!byOwner[p.owner]) byOwner[p.owner] = { count: 0, rev: 0 };
      byOwner[p.owner].count++;
      byOwner[p.owner].rev += p.price * p.vel * 30;
    });
    return {
      text: "Répartition des produits par owner :",
      blocks: [{ kind: "list", items: Object.entries(byOwner).map(([k, v]) => `**${k}** — ${v.count} produits · CA proj 30j ${$(Math.round(v.rev))}`) }],
    };
  }

  // ── Recommandations quotidiennes (NEW)
  if (/recommand|opportunit|sourcing|fournisseur.*jour|produit.*jour|top du jour/.test(q)) {
    if (typeof window.generateDailyRecommendations !== 'function') {
      return { text: "Le moteur de recommandations n'est pas encore chargé. Attends quelques secondes et réessaye." };
    }
    const recs = window.generateDailyRecommendations(P, 10);
    if (!recs.length) {
      return { text: "Aucune recommandation disponible pour aujourd'hui. Assurez-vous que les produits sont chargés." };
    }
    const top = recs[0];
    const avgMargin = (recs.slice(0, 3).reduce((sum, r) => sum + (r.margin || 0), 0) / 3).toFixed(1);
    return {
      text: `**${recs.length} opportunités de sourcing** trouvées pour aujourd'hui · Score moyen: ${(recs.reduce((sum, r) => sum + r.score, 0) / recs.length).toFixed(0)}/100`,
      blocks: [
        { kind: "list", items: recs.slice(0, 5).map(r => `**${r.score}/100** — ${r.title.substring(0, 50)} · Marge ${r.margin}% · BSR #${r.bsr}`) },
        { kind: "note", text: `Top recommandation: ${top.title} (Score ${top.score}/100, Marge ${top.margin}%). Marge moyenne des 3 meilleurs: ${avgMargin}%.` }
      ],
    };
  }

  // ── Alerts
  if (/alerte|risque/.test(q)) {
    const red = A.filter(a => a.tone === "red");
    const amber = A.filter(a => a.tone === "amber");
    return {
      text: `**${A.length} alertes** · ${red.length} critiques · ${amber.length} attention`,
      blocks: [{ kind: "list", items: A.slice(0, 6).map(a => `${a.tone === "red" ? "🔴" : a.tone === "amber" ? "🟠" : "🟢"} **${a.title}** — ${a.sub}`) }],
    };
  }

  // ── Action plan (top 3 priorities)
  if (/action|priorité|plan|cette semaine|à faire|todo/.test(q)) {
    const ruptures = P.filter(p => p.vel > 0 && p.stock > 0 && (p.stock / p.vel) < 7).length;
    const deadStock = P.filter(p => p.vel === 0 && p.stock > 0).length;
    const heavyFees = P.filter(p => p.fees / p.price > 0.32).length;
    const actions = [];
    if (ruptures) actions.push(`🔴 **Réapprovisionner ${ruptures} produits** en zone <7j (rupture imminente)`);
    if (heavyFees) actions.push(`🟠 **Réviser PPC sur ${heavyFees} produits** (frais Amazon >32% du prix)`);
    if (deadStock) actions.push(`🟡 **Liquider/promo ${deadStock} produits** en stock mort (capital bloqué)`);
    actions.push(`🟢 **Pousser PPC** sur les opportunités sous-exploitées (marge ≥27%, BSR >3000)`);
    return {
      text: "Plan d'action prioritaire cette semaine :",
      blocks: [{ kind: "list", items: actions.slice(0, 4) }],
    };
  }

  // ── Catalog summary
  if (/résumé|summary|catalog|combien.*produit|combien.*fournisseur|combien.*sku/.test(q)) {
    const cats = new Set(P.map(p => p.cat));
    const inStock = P.filter(p => p.stock > 0).length;
    const totalRev30 = P.reduce((a, p) => a + p.price * p.vel * 30, 0);
    return {
      text: "**Résumé du catalogue KCC**",
      blocks: [{ kind: "list", items: [
        `**${P.length} produits** · ${inStock} en stock · ${cats.size} catégories`,
        `**${S.length} fournisseurs** actifs`,
        `CA proj 30j: **${$(Math.round(totalRev30))}**`,
        `Marge moyenne: **${(P.reduce((a, p) => a + p.margin, 0) / P.length).toFixed(1)}%**`,
      ]}],
    };
  }

  // ── Tour complet du site (vue d'ensemble globale)
  if (/tour.*site|vue d'?ensemble|résumé.*site|état.*global|état.*entreprise|recap.*global|où.*en.*sommes|where are we|status.*global/.test(q)) {
    const T = window.TASKS || [], SH = window.SHIPMENTS || [], E = window.KCC_EXPENSES || [], M = window.KCC_MESSAGES || [];
    const ruptures = P.filter(p => p.vel > 0 && p.stock > 0 && (p.stock / p.vel) < 14).length;
    const deadStock = P.filter(p => p.vel === 0 && p.stock > 0).length;
    const tasksOpen = T.filter(t => t.status !== "fait").length;
    const openBal = S.reduce((a, s) => a + (+s.openBalance || 0), 0);
    const recentRev = R.slice(-3).reduce((a, x) => a + x.rev, 0);
    const recentProf = R.slice(-3).reduce((a, x) => a + x.profit, 0);
    const lastMsg = M[M.length - 1];
    return {
      text: `**Tour complet du site KCC** — état au ${new Date().toLocaleDateString("fr-FR")}`,
      blocks: [{ kind: "list", items: [
        `**Catalogue** — ${P.length} produits · ${ruptures} en rupture <14j · ${deadStock} en stock mort`,
        `**Fournisseurs** — ${S.length} actifs · ${$(Math.round(openBal))} comptes ouverts`,
        `**Finances** — 3 derniers mois: ${$(recentRev)} revenus → ${$(recentProf)} profit (${(recentProf/recentRev*100).toFixed(1)}%)`,
        `**Dépenses** — ${E.length} entrées suivies (TPS/TVQ Québec)`,
        `**Tâches équipe** — ${tasksOpen} ouvertes · ${A.length} alertes`,
        `**Expéditions FBA** — ${SH.length} en cours`,
        `**Chat équipe** — ${M.length} messages${lastMsg ? ` · dernier: ${lastMsg.authorName}` : ""}`,
      ]}, { kind: "note", text: "Demande-moi un détail sur n'importe quelle section, ou tape /go/<page> dans le chat équipe." }],
    };
  }

  // ── Tasks (TeamBoard)
  if (/(tâche|tache|task|todo|à faire|à completer).*(?:équipe|carly|kevin|clarens|en retard|aujourd|urgent|ouvert)?/.test(q) && !/dépense|facture|message/.test(q)) {
    const T = window.TASKS || [];
    if (!T.length) {
      return { text: "Aucune tâche dans le TeamBoard. Ouvre la page pour en créer.", toolCalls: [{ name: "navigate", input: { page: "team-board" } }] };
    }
    const open = T.filter(t => t.status !== "fait");
    const byOwner = {};
    open.forEach(t => { byOwner[t.assignee || "—"] = (byOwner[t.assignee || "—"] || 0) + 1; });
    return {
      text: `**${open.length} tâches ouvertes** (sur ${T.length} total)`,
      blocks: [
        { kind: "list", items: Object.entries(byOwner).map(([u, n]) => `**${u}** — ${n} tâche(s)`) },
        { kind: "list", items: open.slice(0, 8).map(t => `**${t.title || t.name || "(sans titre)"}** — ${t.assignee || "—"} · ${t.status || "todo"}`) },
      ],
    };
  }

  // ── Shipments FBA
  if (/expédition|expedition|shipment|fba|livraison.*amazon|cartons?|envoi/.test(q)) {
    const SH = window.SHIPMENTS || [];
    if (!SH.length) {
      return { text: "Aucune expédition FBA en cours.", toolCalls: [{ name: "navigate", input: { page: "shipments" } }] };
    }
    return {
      text: `**${SH.length} expéditions FBA** en cours`,
      blocks: [{ kind: "list", items: SH.slice(0, 8).map(s => `**${s.id || s.name || "Shipment"}** — ${s.status || "—"} · ${s.dest || s.destination || "—"} · ${s.units || s.qty || "?"}u`) }],
      toolCalls: [{ name: "navigate", input: { page: "shipments" } }],
    };
  }

  // ── Recent messages
  if (/dernier.*message|message.*récent|chat.*recent|que.*dit.*kevin|que.*dit.*carly|équipe.*dit/.test(q)) {
    const M = window.KCC_MESSAGES || [];
    if (!M.length) {
      return { text: "Aucun message dans le chat équipe. Lance la conversation.", toolCalls: [{ name: "navigate", input: { page: "messages" } }] };
    }
    const recent = M.slice(-6);
    return {
      text: `**${recent.length} derniers messages** (sur ${M.length} total)`,
      blocks: [{ kind: "list", items: recent.map(m => {
        const d = new Date(m.ts || 0);
        const time = `${String(d.getHours()).padStart(2,"0")}:${String(d.getMinutes()).padStart(2,"0")}`;
        return `**${m.authorName || m.author}** (${time}) — ${(m.text || "").slice(0, 100)}`;
      }) }],
    };
  }

  // ── Expenses recap (T2125 / TP-80)
  if (/dépense|depense|expense|impôt|impot|t2125|tp.?80|tps|tvq/.test(q)) {
    const E = window.KCC_EXPENSES || [];
    if (!E.length) {
      return {
        text: "Aucune dépense chargée en mémoire. Ouvre la page Dépenses pour les saisir.",
        toolCalls: [{ name: "navigate", input: { page: "expenses" } }],
      };
    }
    const TPS = 0.05, TVQ = 0.09975;
    const annualize = (a, r) => r === "monthly" ? a*12 : r === "quarterly" ? a*4 : r === "yearly" ? a : a;
    const byCat = {};
    E.forEach((e) => {
      const ann = annualize(+e.amount_pretax || 0, e.recurrence) * (1 + (e.taxable ? TPS + TVQ : 0));
      byCat[e.cat] = (byCat[e.cat] || 0) + ann;
    });
    const rows = Object.entries(byCat).sort((a, b) => b[1] - a[1]);
    const total = rows.reduce((a, [, v]) => a + v, 0);
    return {
      text: `**Récap dépenses annualisées (TTC)** — ${E.length} entrées · total **${$(Math.round(total))}/an**`,
      blocks: [{ kind: "list", items: rows.map(([cat, v]) => `**${cat}** — ${$(Math.round(v))}/an`) }],
    };
  }

  // ── Receipts missing (factures)
  if (/facture|receipt|justificatif|preuve d'achat/.test(q)) {
    const E = window.KCC_EXPENSES || [];
    const missing = E.filter((e) => !(e.receiptIds && e.receiptIds.length));
    if (!missing.length) return { text: "Toutes les dépenses ont une facture rattachée ✓" };
    return {
      text: `**${missing.length} dépense(s) sans facture** — à scanner pour les impôts :`,
      blocks: [
        { kind: "list", items: missing.slice(0, 10).map((e) => `**${e.vendor}** · ${e.recurrence} · ${$(+e.amount_pretax || 0)}`) },
        { kind: "note", text: "Ouvre Dépenses → bouton Photo pour rattacher la facture (image ou PDF, max 5 MB)." },
      ],
      toolCalls: [{ name: "navigate", input: { page: "expenses" } }],
    };
  }

  // ── Send message to team chat
  // Pattern: "envoie/dis à <@kevin|@carly|@clarens|kevin|carly|équipe> ..."
  const msgMatch = prompt.match(/(?:envoie|dis|notifie|écris|ecris|message|poste).{0,30}?(?:à|aux|a)?\s*(@?\w+|équipe|equipe|tous)[\s:,-]+(.+)/i);
  if (msgMatch) {
    const target = msgMatch[1].toLowerCase().replace("@", "");
    let body = msgMatch[2].trim().replace(/^["«']|["»']$/g, "");
    const knownUsers = ["kevin", "carly", "clarens"];
    let mention = "";
    if (knownUsers.includes(target)) mention = `@${target} `;
    else if (target === "équipe" || target === "equipe" || target === "tous") mention = "@kevin @carly ";
    const fullText = mention + body;
    const me = (window.useCurrentUser ? window.useCurrentUser()[0] : null) || { id: "clarens", name: "Clarens" };
    const msg = {
      id: `m-${Date.now()}-jv${Math.random().toString(36).slice(2, 5)}`,
      author: me.id,
      authorName: me.name,
      text: fullText,
      ts: Date.now(),
    };
    return {
      text: `Brouillon prêt à envoyer à l'équipe :\n\n> ${fullText}\n\nConfirme avec **/send** ou ouvre Messages pour ajuster.`,
      blocks: [{ kind: "note", text: "JARVIS peut aussi envoyer directement — réponds 'oui envoie' pour confirmer." }],
      toolCalls: [{ name: "send_message", input: msg }],
    };
  }

  // ── Default
  return {
    text: "Je ne suis pas sûr de comprendre. Tape **/help** pour voir tout ce que je peux faire, ou un ASIN (B0XXXXXXXX) pour un récap produit.",
    blocks: [{ kind: "note", text: "Exemples: \"top marges\", \"ruptures\", \"plan d'action\", \"trésorerie\", \"dépenses récap\", \"factures manquantes\", \"envoie à @kevin: regarde /go/research\"." }],
  };
}

// ─────────────────────────────────────────────────────────────
// Backend caller — cascade Workers AI → Claude → Local
// ─────────────────────────────────────────────────────────────
function buildState() {
  return {
    products: (window.PRODUCTS || []).map(p => ({ asin: p.asin, title: p.name, price: p.price, margin: p.margin, bsr: p.bsr, stock: p.stock, status: p.status })),
    suppliers: (window.SUPPLIERS || []).map(s => ({ name: s.name, country: s.region, score: s.score, moq: s.moq, leadDays: s.leadDays })),
    finances: {
      revenue: (window.REVENUE_MONTHLY || []).map(m => m.rev),
      expenses: (window.REVENUE_MONTHLY || []).map(m => m.rev - m.profit),
    },
  };
}

async function callEndpoint(url, messages) {
  const res = await fetch(url, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ messages, state: buildState() }),
  });
  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    const e = new Error(err.error || `HTTP ${res.status}`);
    e.status = res.status;
    throw e;
  }
  return res.json();
}

// Cascade : Workers AI (gratuit) → Claude → Gemini, ou choix forcé
async function callJarvisAPI(messages, providerPref = "auto") {
  const order = providerPref === "claude"
    ? ["/api/jarvis-action"]
    : providerPref === "gemini"
      ? ["/api/jarvis-gemini"]
      : providerPref === "workers"
        ? ["/api/jarvis-workers-ai"]
        : ["/api/jarvis-workers-ai", "/api/jarvis-action", "/api/jarvis-gemini"];

  const labelFor = (url) =>
    url.includes("workers") ? "workers"
    : url.includes("gemini") ? "gemini"
    : "claude";

  let lastErr;
  for (const url of order) {
    try {
      const data = await callEndpoint(url, messages);
      data._provider = labelFor(url);
      return data;
    } catch (err) {
      lastErr = err;
      // Only fall through to next provider on 503 (binding missing) — otherwise propagate
      if (err.status !== 503) throw err;
    }
  }
  throw lastErr || new Error("No provider available");
}

// ─────────────────────────────────────────────────────────────
// Render helpers
// ─────────────────────────────────────────────────────────────
const renderInline = (txt) => {
  // Bold **xx**, inline code `xx`
  let html = String(txt)
    .replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
    .replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>")
    .replace(/`([^`]+)`/g, '<code style="background:var(--bg-3);padding:1px 5px;border-radius:4px;font-size:11.5px">$1</code>');
  return html;
};

const ToolCallChip = ({ name, input }) => {
  const summary = input?.page ? `→ ${input.page}` : input?.asin ? `· ${input.asin}` : input?.name ? `· ${input.name}` : input?.member ? `· ${input.member}` : "";
  return (
    <span className="row" style={{
      gap: 5, padding: "3px 8px", borderRadius: 999, background: "var(--accent-soft)",
      border: "1px solid oklch(0.72 0.17 248 / 0.25)", fontSize: 10.5, color: "var(--accent)"
    }}>
      <Icon name="bot" size={10} /> <span className="mono">{name}</span> {summary && <span style={{ opacity: 0.7 }}>{summary}</span>}
    </span>
  );
};

// ─────────────────────────────────────────────────────────────
// Component
// ─────────────────────────────────────────────────────────────
const Jarvis = () => {
  const [thread, setThread] = useState(() => {
    try {
      const raw = localStorage.getItem(JARVIS_STORAGE_KEY);
      if (raw) return JSON.parse(raw);
    } catch (_) {}
    return [];
  });
  const [input, setInput] = useState("");
  const [running, setRunning] = useState(false);
  const [provider, setProvider] = useState(() => localStorage.getItem("kcc_jarvis_provider") || "auto"); // auto | workers | claude | local
  const [apiStatus, setApiStatus] = useState("unknown"); // unknown | workers | claude | local | error
  const textareaRef = useRef(null);
  const endRef = useRef(null);
  const toast = (window.useToast || (() => () => {}))();

  // Persist
  useEffect(() => {
    try { localStorage.setItem(JARVIS_STORAGE_KEY, JSON.stringify(thread.slice(-50))); } catch (_) {}
  }, [thread]);

  // Auto-scroll
  useEffect(() => {
    endRef.current?.scrollIntoView({ behavior: "smooth", block: "end" });
  }, [thread, running]);

  // Auto-resize textarea
  useEffect(() => {
    if (!textareaRef.current) return;
    textareaRef.current.style.height = "auto";
    textareaRef.current.style.height = Math.min(textareaRef.current.scrollHeight, 140) + "px";
  }, [input]);

  const now = () => new Date().toLocaleTimeString("fr-FR", { hour: "2-digit", minute: "2-digit" });

  const executeToolCalls = (toolCalls) => {
    const executed = [];
    for (const tc of toolCalls || []) {
      if (tc.name === "navigate" && tc.input?.page) {
        const pageMap = {
          db: "dashboard", produits: "research", fournisseurs: "supplier",
          discover: "opportunity", fba: "shipments", analyses: "finance",
          comparatif: "supplier-cmp", alertes: "alerts", finances: "finance",
          calendrier: "calendar", centre: "prep", expeditions: "shipments",
          retours: "returns", asinlib: "asin", prep: "prep", import: "import",
          parametres: "settings", jarvis: "jarvis",
          depenses: "expenses", expenses: "expenses", factures: "expenses",
          messages: "messages", chat: "messages",
        };
        const target = pageMap[tc.input.page] || tc.input.page;
        if (window.kccNavigate) {
          setTimeout(() => window.kccNavigate(target), 250);
          executed.push(`Navigation → ${target}`);
        }
      } else if (tc.name === "add_product" && tc.input?.asin && tc.input?.title) {
        const prod = {
          id: tc.input.asin,
          asin: tc.input.asin,
          name: tc.input.title,
          title: tc.input.title,
          price: +tc.input.price || 0,
          cost: +tc.input.cost || 0,
          fees: +tc.input.fees || 0,
          margin: +tc.input.margin || 0,
          roi: +tc.input.roi || 0,
          bsr: +tc.input.bsr || 0,
          stock: +tc.input.stock || 0,
          vel: +tc.input.vel || 0,
          cat: tc.input.category || tc.input.cat || "Autre",
          status: tc.input.status || "pending",
          owner: tc.input.owner || "clarens",
          supplier: tc.input.supplier || "—",
          source: "jarvis",
          createdAt: new Date().toISOString(),
        };
        if ((window.PRODUCTS || []).some(p => p.asin === prod.asin)) {
          toast?.({ message: `ASIN ${prod.asin} déjà au catalogue`, tone: "amber" });
        } else {
          (window.PRODUCTS || []).push(prod);
          fetch("/api/sync", {
            method: "PUT", headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ col: "products", items: [prod] }),
          }).then(() => toast?.({ message: `Produit ajouté: ${prod.asin}`, tone: "success" })).catch(() => {});
          executed.push(`Produit + ${prod.asin}`);
        }
      } else if (tc.name === "update_product" && tc.input?.asin && tc.input?.updates) {
        const p = (window.PRODUCTS || []).find(x => x.asin === tc.input.asin);
        if (p) {
          Object.assign(p, tc.input.updates, { updatedAt: new Date().toISOString() });
          fetch("/api/sync", {
            method: "PUT", headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ col: "products", items: [p] }),
          }).catch(() => {});
          toast?.({ message: `Produit maj: ${p.asin}`, tone: "success" });
          executed.push(`Produit maj ${p.asin}`);
        }
      } else if (tc.name === "delete_product" && tc.input?.asin) {
        const arr = window.PRODUCTS || [];
        const idx = arr.findIndex(x => x.asin === tc.input.asin);
        if (idx >= 0) {
          arr.splice(idx, 1);
          fetch(`/api/sync?col=products&id=${encodeURIComponent(tc.input.asin)}`, { method: "DELETE" }).catch(() => {});
          toast?.({ message: `Produit supprimé: ${tc.input.asin}`, tone: "muted" });
          executed.push(`Produit - ${tc.input.asin}`);
        }
      } else if (tc.name === "add_supplier" && tc.input?.name) {
        const sup = {
          id: tc.input.id || `sup-${Date.now()}`,
          name: tc.input.name,
          country: tc.input.country || "",
          region: tc.input.country || tc.input.region || "—",
          contact: tc.input.contact || "",
          email: tc.input.email || "",
          phone: tc.input.phone || "",
          score: +tc.input.score || 0,
          moq: +tc.input.moq || 0,
          leadDays: +tc.input.leadDays || 0,
          terms: tc.input.terms || "—",
          openBalance: +tc.input.openBalance || 0,
          status: tc.input.status || "actif",
          notes: tc.input.notes || "",
          source: "jarvis",
          createdAt: new Date().toISOString(),
        };
        if ((window.SUPPLIERS || []).some(s => (s.name || "").toLowerCase() === sup.name.toLowerCase())) {
          toast?.({ message: `Fournisseur déjà au catalogue`, tone: "amber" });
        } else {
          (window.SUPPLIERS || []).push(sup);
          fetch("/api/sync", {
            method: "PUT", headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ col: "suppliers", items: [sup] }),
          }).then(() => toast?.({ message: `Fournisseur ajouté: ${sup.name}`, tone: "success" })).catch(() => {});
          executed.push(`Fournisseur + ${sup.name}`);
        }
      } else if (tc.name === "update_supplier" && tc.input?.name && tc.input?.updates) {
        const s = (window.SUPPLIERS || []).find(x => (x.name || "").toLowerCase() === tc.input.name.toLowerCase());
        if (s) {
          Object.assign(s, tc.input.updates, { updatedAt: new Date().toISOString() });
          fetch("/api/sync", {
            method: "PUT", headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ col: "suppliers", items: [s] }),
          }).catch(() => {});
          toast?.({ message: `Fournisseur maj: ${s.name}`, tone: "success" });
          executed.push(`Fournisseur maj ${s.name}`);
        }
      } else if (tc.name === "delete_supplier" && tc.input?.name) {
        const arr = window.SUPPLIERS || [];
        const idx = arr.findIndex(x => (x.name || "").toLowerCase() === tc.input.name.toLowerCase());
        if (idx >= 0) {
          const removed = arr.splice(idx, 1)[0];
          if (removed?.id) fetch(`/api/sync?col=suppliers&id=${encodeURIComponent(removed.id)}`, { method: "DELETE" }).catch(() => {});
          toast?.({ message: `Fournisseur supprimé`, tone: "muted" });
          executed.push(`Fournisseur - ${tc.input.name}`);
        }
      } else if (tc.name === "add_calendar_event" && tc.input?.date && tc.input?.label) {
        const ev = {
          id: tc.input.id || `ev-${Date.now()}`,
          date: tc.input.date,
          label: tc.input.label,
          color: tc.input.color || "var(--accent)",
          source: "jarvis",
        };
        fetch("/api/sync", {
          method: "PUT", headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ col: "tasks", items: [{ ...ev, kind: "calendar" }] }),
        }).then(() => toast?.({ message: `Événement ajouté: ${ev.date}`, tone: "success" })).catch(() => {});
        executed.push(`Événement ${ev.date}`);
      } else if (tc.name === "run_discover") {
        fetch("/api/discover/run?force=1", { method: "POST" })
          .then(r => r.json())
          .then(j => toast?.({ message: `Découverte: ${j.suppliers || 0} fourn., ${j.products || 0} prod.`, tone: "success" }))
          .catch(() => toast?.({ message: `Découverte indisponible`, tone: "red" }));
        executed.push(`run_discover`);
      } else if (tc.name === "sync_amazon") {
        Promise.all([
          fetch("/api/finance").then(r => r.ok ? r.json() : null).catch(() => null),
          fetch("/api/inventory").then(r => r.ok ? r.json() : null).catch(() => null),
        ]).then(([fin, inv]) => {
          if (inv?.products && Array.isArray(inv.products)) {
            window.PRODUCTS.length = 0;
            window.PRODUCTS.push(...inv.products);
          }
          toast?.({ message: `Amazon sync ✓`, tone: "success" });
        });
        executed.push(`sync_amazon`);
      } else if (tc.name === "add_finance_month" && tc.input?.month) {
        const row = {
          id: `fin-${tc.input.month}`,
          m: tc.input.month,
          rev: +tc.input.revenue || 0,
          profit: (+tc.input.revenue || 0) - (+tc.input.expenses || 0),
        };
        const arr = window.REVENUE_MONTHLY || [];
        const idx = arr.findIndex(x => x.m === row.m);
        if (idx >= 0) arr[idx] = row; else arr.push(row);
        fetch("/api/sync", {
          method: "PUT", headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ col: "finances", items: [row] }),
        }).catch(() => {});
        toast?.({ message: `Mois ${row.m} enregistré`, tone: "success" });
        executed.push(`Mois ${row.m}`);
      } else if (tc.name === "set_wealth_goal" && tc.input?.goal != null) {
        try { localStorage.setItem("kcc_wealth_goal", String(+tc.input.goal)); } catch (_) {}
        toast?.({ message: `Objectif: $${(+tc.input.goal).toLocaleString()}`, tone: "success" });
        executed.push(`Wealth goal $${(+tc.input.goal).toLocaleString()}`);
      } else if (tc.name === "add_task" && tc.input?.title) {
        const task = {
          id: tc.input.id || `t-${Date.now()}-${Math.random().toString(36).slice(2, 5)}`,
          title: tc.input.title,
          assignee: tc.input.assignee || "clarens",
          status: tc.input.status || "todo",
          priority: tc.input.priority || "medium",
          due: tc.input.due || null,
          createdAt: Date.now(),
        };
        (window.TASKS || []).push(task);
        fetch("/api/sync", {
          method: "PUT",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ col: "tasks", items: [task] }),
        }).then(() => toast?.({ message: `Tâche ajoutée: ${task.title}`, tone: "success" })).catch(() => {});
        executed.push(`Tâche → ${task.assignee}`);
      } else if (tc.name === "complete_task" && tc.input?.id) {
        const t = (window.TASKS || []).find(x => x.id === tc.input.id);
        if (t) {
          t.status = "fait";
          fetch("/api/sync", {
            method: "PUT",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ col: "tasks", items: [t] }),
          }).catch(() => {});
          toast?.({ message: `Tâche complétée: ${t.title}`, tone: "success" });
          executed.push(`Tâche complétée`);
        }
      } else if (tc.name === "add_expense" && tc.input?.vendor && tc.input?.amount_pretax != null) {
        const exp = {
          id: tc.input.id || `exp-${Date.now()}`,
          vendor: tc.input.vendor,
          cat: tc.input.cat || "autre",
          recurrence: tc.input.recurrence || "monthly",
          amount_pretax: +tc.input.amount_pretax,
          currency: tc.input.currency || "CAD",
          taxable: tc.input.taxable !== false,
          splits: tc.input.splits || { clarens: 33.34, kevin: 33.33, carly: 33.33 },
          note: tc.input.note || "",
          paidBy: tc.input.paidBy || "",
          created_at: Date.now(),
        };
        (window.KCC_EXPENSES || []).push(exp);
        fetch("/api/sync", {
          method: "PUT",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ col: "expenses", items: [exp] }),
        }).then(() => toast?.({ message: `Dépense ajoutée: ${exp.vendor}`, tone: "success" })).catch(() => {});
        executed.push(`Dépense → ${exp.vendor}`);
      } else if (tc.name === "send_message" && tc.input?.text) {
        // Post message to team chat (D1 col=messages)
        const msg = {
          id: tc.input.id || `m-${Date.now()}-jv${Math.random().toString(36).slice(2, 5)}`,
          author: tc.input.author || "clarens",
          authorName: tc.input.authorName || "Clarens",
          text: tc.input.text,
          ts: tc.input.ts || Date.now(),
        };
        fetch("/api/sync", {
          method: "PUT",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ col: "messages", items: [msg] }),
        }).then(() => {
          toast?.({ message: "Message envoyé à l'équipe", tone: "success" });
        }).catch(() => {
          toast?.({ message: "Échec d'envoi (offline ?)", tone: "red" });
        });
        executed.push(`Message → équipe KCC`);
      } else if (tc.name === "add_product_photo" && tc.input?.asin && tc.input?.image_url) {
        const p = (window.PRODUCTS || []).find(x => x.asin === tc.input.asin);
        if (p) {
          p.image = tc.input.image_url;
          fetch("/api/sync", {
            method: "PUT", headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ col: "products", items: [{ ...p }] }),
          }).catch(() => {});
          toast?.({ message: `Photo ajoutée à ${p.asin}`, tone: "success" });
          executed.push(`Photo → ${p.asin}`);
        } else {
          toast?.({ message: `ASIN ${tc.input.asin} introuvable`, tone: "amber" });
        }
      } else if (tc.name === "find_product_image" && tc.input?.asin) {
        // Async: chercher l'image puis l'attacher au produit
        (async () => {
          try {
            const res = await fetch("/api/find-product-image", {
              method: "POST",
              headers: { "Content-Type": "application/json" },
              body: JSON.stringify({ asin: tc.input.asin, title: tc.input.title }),
            });
            if (!res.ok) throw new Error(`HTTP ${res.status}`);
            const data = await res.json();
            if (data.image_url) {
              const p = (window.PRODUCTS || []).find(x => x.asin === tc.input.asin);
              if (p) {
                p.image = data.image_url;
                fetch("/api/sync", {
                  method: "PUT", headers: { "Content-Type": "application/json" },
                  body: JSON.stringify({ col: "products", items: [{ ...p }] }),
                }).catch(() => {});
              }
              toast?.({ message: `Image trouvée pour ${tc.input.asin} (${data.source})`, tone: "success" });
            } else {
              toast?.({ message: `Aucune image trouvée pour ${tc.input.asin}`, tone: "amber" });
            }
          } catch (e) {
            toast?.({ message: `Recherche image: ${e.message}`, tone: "red" });
          }
        })();
        executed.push(`Recherche image · ${tc.input.asin}`);
      } else if (tc.name === "add_supplier_contact" && tc.input?.supplier_name && tc.input?.phone) {
        const sup = (window.SUPPLIERS || []).find(s =>
          s.name?.toLowerCase() === String(tc.input.supplier_name).toLowerCase()
        );
        if (sup) {
          const phones = Array.isArray(sup.phones) ? [...sup.phones] : [];
          phones.push({
            role: tc.input.role || "Commercial",
            name: tc.input.name || "",
            email: tc.input.email || "",
            number: tc.input.phone,
          });
          sup.phones = phones;
          if (!sup.phone) sup.phone = tc.input.phone;
          fetch("/api/sync", {
            method: "PUT", headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ col: "suppliers", items: [{ ...sup }] }),
          }).catch(() => {});
          toast?.({ message: `Contact ajouté à ${sup.name}`, tone: "success" });
          executed.push(`Contact → ${sup.name}`);
        } else {
          toast?.({ message: `Fournisseur "${tc.input.supplier_name}" introuvable`, tone: "amber" });
        }
      } else if (tc.name === "send_notification" && tc.input?.title) {
        // Inject as alert (visible dans la cloche)
        const alert = {
          id: `notif-${Date.now()}`,
          title: tc.input.title,
          description: tc.input.description || "",
          level: tc.input.level || "info",
          ts: Date.now(),
          source: "jarvis",
        };
        (window.ALERTS = window.ALERTS || []).push(alert);
        fetch("/api/sync", {
          method: "PUT", headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ col: "alerts", items: [alert] }),
        }).catch(() => {});
        toast?.({ message: `🔔 ${alert.title}`, tone: alert.level === "danger" ? "red" : "blue" });
        executed.push(`Notification · ${alert.title}`);
      } else if (tc.name === "add_task" && tc.input?.title) {
        const task = {
          id: `task-${Date.now()}`,
          title: tc.input.title,
          assignee: tc.input.assignee || "clarens",
          status: tc.input.status || "todo",
          createdAt: new Date().toISOString(),
          source: "jarvis",
        };
        (window.TASKS = window.TASKS || []).push(task);
        fetch("/api/sync", {
          method: "PUT", headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ col: "tasks", items: [task] }),
        }).catch(() => {});
        toast?.({ message: `Tâche → ${task.assignee}: ${task.title}`, tone: "success" });
        executed.push(`Tâche · ${task.assignee}`);
      } else if (tc.name === "add_expense" && tc.input?.amount_pretax) {
        const exp = {
          id: `exp-${Date.now()}`,
          vendor: tc.input.vendor || "—",
          amount_pretax: +tc.input.amount_pretax,
          cat: tc.input.cat || "autre",
          date: tc.input.date || new Date().toISOString().slice(0, 10),
          taxable: tc.input.taxable !== false,
          recurrence: tc.input.recurrence || "ponctuel",
          notes: tc.input.notes || "",
          paidBy: tc.input.paidBy || "carte",
          source: "jarvis",
        };
        (window.KCC_EXPENSES = window.KCC_EXPENSES || []).push(exp);
        fetch("/api/sync", {
          method: "PUT", headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ col: "expenses", items: [exp] }),
        }).catch(() => {});
        toast?.({ message: `Dépense ${exp.vendor} · $${exp.amount_pretax}`, tone: "success" });
        executed.push(`Dépense · ${exp.vendor}`);
      }
    }
    return executed;
  };

  const send = async (rawText) => {
    const text = (rawText || "").trim();
    if (!text || running) return;

    // Slash commands
    if (text === "/reset" || text === "/clear") {
      setThread([]);
      toast?.({ message: "Conversation effacée", tone: "muted" });
      setInput("");
      return;
    }
    if (text === "/help") {
      setThread(prev => [...prev,
        { role: "user", text, time: now() },
        { role: "assistant", text: "Commandes disponibles :\n• `/reset` ou `/clear` — efface la conversation\n• `/help` — affiche ce message\n• Tape un ASIN (B0XXXXXXXX) pour un récap\n• Pose toute question business — JARVIS répond avec accès à PRODUCTS, SUPPLIERS, REVENUE", time: now() }
      ]);
      setInput("");
      return;
    }

    const userMsg = { role: "user", text, time: now() };
    setThread(prev => [...prev, userMsg]);
    setInput("");
    setRunning(true);

    // Build messages for API: only role + content (string)
    const apiMessages = [...thread, userMsg]
      .filter(m => m.role === "user" || m.role === "assistant")
      .map(m => ({ role: m.role, content: m.text }));

    try {
      if (provider === "local") throw new Error("forced local");
      const data = await callJarvisAPI(apiMessages, provider);
      setApiStatus(data._provider || "claude");
      const executedTools = executeToolCalls(data.toolCalls);
      setThread(prev => [...prev, {
        role: "assistant",
        text: data.text || "(réponse vide)",
        toolCalls: data.toolCalls || [],
        executed: executedTools,
        providerUsed: data._provider,
        time: now(),
      }]);
    } catch (err) {
      // Fallback to local intent router
      setApiStatus(err.status === 503 ? "local" : "error");
      const ans = localAnswer(text);
      executeToolCalls(ans.toolCalls);
      setThread(prev => [...prev, {
        role: "assistant",
        text: ans.text,
        blocks: ans.blocks,
        toolCalls: ans.toolCalls || [],
        fallback: true,
        time: now(),
      }]);
    } finally {
      setRunning(false);
    }
  };

  const onKeyDown = (e) => {
    if (e.key === "Enter" && !e.shiftKey) {
      e.preventDefault();
      send(input);
    }
  };

  const statusBadge = apiStatus === "workers"
    ? <Badge tone="green">● Workers AI (Llama 3.1 · gratuit)</Badge>
    : apiStatus === "claude"
      ? <Badge tone="green">● Claude Sonnet 4.6</Badge>
      : apiStatus === "gemini"
        ? <Badge tone="green">● Gemini 2.5 Pro</Badge>
        : apiStatus === "local"
          ? <Badge tone="amber">Mode local · pas d'IA configurée</Badge>
          : apiStatus === "error"
            ? <Badge tone="red">API erreur · fallback local</Badge>
            : <Badge tone="muted">Prêt</Badge>;

  const setProviderAndPersist = (p) => {
    setProvider(p);
    try { localStorage.setItem("kcc_jarvis_provider", p); } catch (_) {}
  };

  return (
    <div className="page" style={{ maxWidth: 920 }}>
      <div className="page-head">
        <div>
          <div className="page-title">JARVIS</div>
          <div className="page-sub">
            Assistant IA · accès complet au site : produits · fournisseurs · finances · dépenses · tâches · expéditions · messages
            <span className="mono" style={{ marginLeft: 6, color: "var(--text-faint)" }}>Claude · Gemini · Workers AI</span>
          </div>
        </div>
        <div className="row" style={{ gap: 8 }}>
          {statusBadge}
          <div className="row" style={{ gap: 2, padding: 2, background: "var(--bg-2)", borderRadius: 7, border: "1px solid var(--border-subtle)" }}>
            {[
              { id: "auto",    label: "Auto"    },
              { id: "workers", label: "Workers" },
              { id: "claude",  label: "Claude"  },
              { id: "gemini",  label: "Gemini"  },
              { id: "local",   label: "Local"   },
            ].map(opt => (
              <button
                key={opt.id}
                onClick={() => setProviderAndPersist(opt.id)}
                style={{
                  padding: "3px 8px", borderRadius: 5, border: "none", cursor: "pointer",
                  fontSize: 11, fontWeight: 500,
                  background: provider === opt.id ? "var(--bg-3)" : "transparent",
                  color: provider === opt.id ? "var(--text)" : "var(--text-dim)",
                }}
              >{opt.label}</button>
            ))}
          </div>
          {thread.length > 0 && (
            <button className="btn" data-variant="ghost" onClick={() => { setThread([]); toast?.({ message: "Conversation effacée", tone: "muted" }); }}>
              <Icon name="refresh" size={12} /> Effacer
            </button>
          )}
        </div>
      </div>

      {/* Capacités (parité ancien JARVIS) */}
      <div className="row" style={{ gap: 6, marginBottom: 12, flexWrap: "wrap" }}>
        {[
          ["Naviguer", "27 pages"],
          ["Analyser IA", "produits · fournisseurs · finances"],
          ["Calculer FBA", "rentabilité live"],
          ["CRUD", "+/- produits · fournisseurs · tâches"],
          ["Chat équipe", "envoyer messages"],
          ["Sync Amazon", "finance · inventaire"],
          ["Discover", "auto sourcing"],
        ].map(([l, d]) => (
          <div key={l} style={{
            background: "var(--accent-soft)",
            border: "1px solid oklch(0.72 0.17 248 / 0.18)",
            borderRadius: 7, padding: "4px 10px", fontSize: 10.5,
            color: "var(--accent-strong)", fontFamily: "var(--font-mono)",
          }}>
            <span style={{ fontWeight: 700 }}>{l}</span>
            <span style={{ color: "var(--text-faint)", marginLeft: 4 }}>{d}</span>
          </div>
        ))}
      </div>

      {/* Suggested prompts */}
      {thread.length === 0 && (
        <div className="grid" style={{ gridTemplateColumns: "repeat(3, 1fr)", gap: 8, marginBottom: 14 }}>
          {SUGGESTED_PROMPTS.map((p, i) => (
            <button
              key={i}
              onClick={() => send(p.text)}
              className="card"
              style={{ padding: 12, textAlign: "left", cursor: "pointer", border: "1px solid var(--border-subtle)" }}
            >
              <div className="row" style={{ gap: 8, marginBottom: 4 }}>
                <Icon name={p.icon} size={13} style={{ color: "var(--accent)" }} />
                <span style={{ fontSize: 12.5, fontWeight: 550 }}>{p.label}</span>
              </div>
              <div className="muted" style={{ fontSize: 11.5, lineHeight: 1.4 }}>{p.text}</div>
            </button>
          ))}
        </div>
      )}

      {/* Thread */}
      <div className="stack" style={{ gap: 12, marginBottom: 14 }}>
        {thread.map((msg, i) => (
          <div
            key={i}
            style={{
              display: "flex", gap: 10,
              flexDirection: msg.role === "user" ? "row-reverse" : "row",
            }}
          >
            <div
              style={{
                width: 28, height: 28, borderRadius: 999, flexShrink: 0,
                background: msg.role === "user" ? "var(--bg-3)" : "linear-gradient(155deg, var(--accent), oklch(0.55 0.18 258))",
                display: "grid", placeItems: "center",
                color: "white", fontSize: 11, fontWeight: 700,
              }}
            >
              {msg.role === "user" ? "C" : (window.JarvisLogo ? <window.JarvisLogo size={14}/> : <Icon name="bot" size={14} />)}
            </div>
            <div
              className="card"
              style={{
                maxWidth: "78%", padding: 12,
                background: msg.role === "user" ? "var(--accent-soft)" : "var(--bg-1)",
                border: msg.role === "user" ? "1px solid oklch(0.72 0.17 248 / 0.25)" : "1px solid var(--border-subtle)",
              }}
            >
              {msg.text && (
                <div
                  style={{ fontSize: 13, lineHeight: 1.55, whiteSpace: "pre-wrap" }}
                  dangerouslySetInnerHTML={{ __html: renderInline(msg.text) }}
                />
              )}
              {msg.blocks && (
                <div className="stack" style={{ gap: 8, marginTop: 8 }}>
                  {msg.blocks.map((b, j) => (
                    b.kind === "list" ? (
                      <ul key={j} style={{ margin: 0, padding: "0 0 0 18px", fontSize: 12.5, lineHeight: 1.6 }}>
                        {b.items.map((it, k) => <li key={k} dangerouslySetInnerHTML={{ __html: renderInline(it) }} />)}
                      </ul>
                    ) : (
                      <div key={j} style={{ padding: 10, background: "var(--bg-2)", borderRadius: 8, fontSize: 12, color: "var(--text-dim)" }}>{b.text}</div>
                    )
                  ))}
                </div>
              )}
              {!!msg.toolCalls?.length && (
                <div className="row" style={{ gap: 6, marginTop: 8, flexWrap: "wrap" }}>
                  {msg.toolCalls.map((tc, k) => <ToolCallChip key={k} name={tc.name} input={tc.input} />)}
                </div>
              )}
              <div className="row" style={{ gap: 8, marginTop: 6, justifyContent: msg.role === "user" ? "flex-end" : "space-between" }}>
                {msg.fallback && <span className="mono" style={{ fontSize: 10, color: "var(--amber)" }}>fallback local</span>}
                <span className="mono" style={{ fontSize: 10.5, color: "var(--text-faint)" }}>{msg.time}</span>
              </div>
            </div>
          </div>
        ))}
        {running && (
          <div className="row" style={{ gap: 10 }}>
            <div style={{ width: 28, height: 28, borderRadius: 999, background: "linear-gradient(155deg, var(--accent), oklch(0.55 0.18 258))", display: "grid", placeItems: "center" }}>
              {window.JarvisLogo ? <window.JarvisLogo size={14}/> : <Icon name="bot" size={14} />}
            </div>
            <div className="card" style={{ padding: "10px 14px" }}>
              <div className="row" style={{ gap: 4 }}>
                <span style={{ width: 6, height: 6, borderRadius: "50%", background: "var(--text-dim)", animation: "pulse 1.4s infinite" }} />
                <span style={{ width: 6, height: 6, borderRadius: "50%", background: "var(--text-dim)", animation: "pulse 1.4s infinite 0.2s" }} />
                <span style={{ width: 6, height: 6, borderRadius: "50%", background: "var(--text-dim)", animation: "pulse 1.4s infinite 0.4s" }} />
              </div>
            </div>
          </div>
        )}
        <div ref={endRef} />
      </div>

      {/* Input */}
      <form
        onSubmit={(e) => { e.preventDefault(); send(input); }}
        className="card"
        style={{ padding: 10, position: "sticky", bottom: 12, background: "var(--bg-1)" }}
      >
        <div className="row" style={{ gap: 8, alignItems: "flex-end" }}>
          <textarea
            ref={textareaRef}
            value={input}
            onChange={(e) => setInput(e.target.value)}
            onKeyDown={onKeyDown}
            placeholder="Demande à JARVIS… (Entrée pour envoyer, Shift+Entrée pour saut de ligne) · /help"
            rows={1}
            style={{
              flex: 1, background: "transparent", border: "none", outline: "none",
              color: "var(--text)", fontSize: 13, lineHeight: 1.5,
              resize: "none", padding: "8px 10px", fontFamily: "inherit",
              maxHeight: 140, minHeight: 32,
            }}
          />
          <button type="submit" className="btn" data-variant="primary" disabled={running || !input.trim()}>
            <Icon name="arrow-right" size={12} /> {running ? "..." : "Envoyer"}
          </button>
        </div>
        <div className="mono" style={{ fontSize: 10.5, color: "var(--text-faint)", marginTop: 6 }}>
          {apiStatus === "workers" && "Connecté · Cloudflare Workers AI (Llama 3.1 8B · gratuit · 10k neurones/jour)"}
          {apiStatus === "claude" && "Connecté · Claude Sonnet 4.6 avec 13 outils (navigate, add_product, sync_amazon…)"}
          {apiStatus === "local" && "Mode local · analyse en direct de PRODUCTS / SUPPLIERS / REVENUE — 100% gratuit"}
          {(apiStatus === "error" || apiStatus === "unknown") && "Cascade auto: Workers AI (gratuit) → Claude → Gemini → Local. Bascule manuelle via le toggle ci-dessus."}
        </div>
      </form>
    </div>
  );
};

window.Jarvis = Jarvis;
