// =============================================================
// Commandes (shipments) — list + detail + receive workflow
// =============================================================

// Helper : statut rechargements d'un colis (delegue au helper partage shared.jsx)
const _getRechaStatus = (shipment, topups, purchases) => {
  return shipRechargeStatus(shipment, topups, purchases);
};

// Retourne le taux du dernier rechargement avec rate non nul, sinon currentRate, sinon 170
// Garantit de ne jamais retourner 0, undefined ou NaN
const _getLastKnownRate = (topups, currentRate) => {
  if (currentRate && currentRate > 0) return currentRate;
  // cherche parmi tous les topups le plus recent avec un rate valide
  const withRate = (topups || [])
    .filter(t => t.rate && t.rate > 0)
    .sort((a, b) => {
      // tri par date desc (format ISO "YYYY-MM-DD")
      if (a.date && b.date) return b.date.localeCompare(a.date);
      return (b.id || 0) - (a.id || 0);
    });
  if (withRate.length > 0) return withRate[0].rate;
  return 170;
};

// Helper : taux effectif d'un colis (3 cas : confirme via topups / confirme manuel / estimation)
// FIX 3 : fallback = dernier topup avec rate non nul, puis 170. Jamais 0 ni undefined.
const _getEffectiveRate = (shipment, topups, currentRate) => {
  // ADAPTE EXCEL : le taux de la commande (shipment.rate = taux de costing Excel) PRIME
  // sur le taux derive des rechargements. Sinon la conversion EUR derive (ex CMD2/3/4 :
  // topups a 162 vs taux Excel 148/158/160) et la somme des commandes ne colle plus a l'Excel.
  if (shipment.rate) {
    return {
      rate: shipment.rate,
      source: "manual",
      label: "Taux commande (Excel)",
      color: "pos",
    };
  }
  const linked = topups.filter(t => t.shipment === shipment.id);
  const allHaveEur = linked.length > 0 && linked.every(t => t.eur && t.jpy);
  if (allHaveEur) {
    const sumJpy = linked.reduce((a, t) => a + (t.jpy || 0), 0);
    const sumEur = linked.reduce((a, t) => a + (t.eur || 0), 0);
    // fallback si somme EUR = 0 (ne devrait pas arriver mais on protege)
    const rate = sumEur > 0 ? sumJpy / sumEur : _getLastKnownRate(topups, currentRate);
    return {
      rate,
      source: "topups",
      label: `Confirme via ${linked.length} rechargement${linked.length > 1 ? "s" : ""}`,
      color: "pos",
    };
  }
  // estimation : taux du dernier rechargement connu, sinon 170
  const estimRate = _getLastKnownRate(topups, currentRate);
  return {
    rate: estimRate,
    source: "estimation",
    label: "Estimation - en attente de rechargement",
    color: "warn",
  };
};

// Helper : commande a repartir = a des achats attaches ET pas encore livree (workflow receive non joue)
const _needsReception = (shipment, purchases) => {
  if (shipment.status === "delivered") return false;
  const linked = purchases.filter(p => p.ship === shipment.id);
  return linked.length > 0;
};

// Helper : statut stock uniquement (rechargement a sa propre colonne "Recharg.")
// Vert = stock reparti, gris = a repartir, N/A si pas d'achats
const _getStockStatus = (shipment, purchases) => {
  const linked = purchases.filter(p => p.ship === shipment.id);
  if (linked.length === 0) {
    return { label: "N/A", color: "ghost" };
  }
  const distributed = shipment.status === "delivered" || linked.every(p => p.stock_state === "available");
  if (distributed) {
    return { label: "✓ Réparti", color: "pos" };
  }
  return { label: "A répartir", color: "ghost" };
};

// Reconciliation FIGEE CMD1-6 depuis Commandes.patched.xlsx (feuille Rechargements, section RECONCILIATION).
// Le calcul live des topups compte mal (douane payee au transporteur + repartition) -> on hardcode le verdict Excel.
// paidJpy = topups + douane (yen reels) ; totalJpy = total commande (yen reels, feuilles CMDn-Items).
// deltaJpy = paidJpy - totalJpy : negatif = il manque, positif = surplus. Cle = shipment.id (= numero de commande).
// On se cale sur le delta YEN (chiffre reel) et pas le delta EUR de l'Excel (pollue par melange de taux).
const RECON_OVERRIDE = {
  1: { paidJpy: 200011, totalJpy: 201088, deltaJpy: -1077 },
  2: { paidJpy: 69745,  totalJpy: 69844,  deltaJpy: -99 },
  3: { paidJpy: 100511, totalJpy: 89581,  deltaJpy: 10930 },
  4: { paidJpy: 146793, totalJpy: 156792, deltaJpy: -9999 },
  5: { paidJpy: 60267,  totalJpy: 59806,  deltaJpy: 461 },
  6: { paidJpy: 7279,   totalJpy: 8092,   deltaJpy: -813 },
};

// Liste des statuts editables (label affiche + valeur backend)
const _STATUS_OPTIONS = [
  { v: "pending", label: "En attente" },
  { v: "created", label: "Créée" },
  { v: "ordered", label: "Commandée" },
  { v: "in_transit", label: "En transit" },
  { v: "received", label: "Reçue" },
  { v: "delivered", label: "Livrée" },
  { v: "cancelled", label: "Annulée" },
];

const ShipmentsScreen = ({ state, actions, navigate }) => {
  const { shipments, purchases, categories } = state;
  const [filter, setFilter] = useState("all");
  const [openId, setOpenId] = useState(null);
  const [newOpen, setNewOpen] = useState(false);

  // Ordinal stable (1, 2, 3... sans trous) base sur l'ordre de creation (id ASC).
  // Apres suppression d'un colis, les numeros se decalent automatiquement.
  const ordinals = useMemo(() => {
    const sorted = [...shipments].sort((a, b) => a.id - b.id);
    return Object.fromEntries(sorted.map((s, i) => [s.id, i + 1]));
  }, [shipments]);

  // Menu "serie" de la nouvelle commande : limite aux collections presentes dans le stock
  // (evite les categories parasites). "+ Nouvelle serie" reste dispo pour en creer une.
  const seriesCats = useMemo(() => {
    const stockIds = new Set((state.stockSeries || []).map(x => x.cat));
    return categories.filter(c => stockIds.has(c.id));
  }, [categories, state.stockSeries]);

  const filtered = useMemo(() => {
    let r;
    if (filter === "all") r = shipments;
    else if (filter === "active") r = shipments.filter(s => s.status !== "delivered");
    else if (filter === "to_receive") r = shipments.filter(s => _needsReception(s, purchases));
    else r = shipments.filter(s => s.status === "delivered");
    return [...r].sort((a, b) => (b.date || "").localeCompare(a.date || ""));
  }, [shipments, purchases, filter]);

  // Couverture rechargements : map ship.id -> { delta, pct }
  // delta = topupsJpy - totalJpy (positif = trop, negatif = manque)
  const topupCoverage = useMemo(() => {
    const result = {};
    shipments.forEach(s => {
      const cur = s.currency || "JPY";
      if (cur !== "JPY") { result[s.id] = null; return; }
      const items = purchases.filter(p => p.ship === s.id);
      const itemsJpy = items.reduce((a, p) => a + (p.jpy || 0) + (p.port_jp || 0), 0);
      const portJpy = s.port_jpy || s.intl_shipping_jpy || 0;
      const totalJpy = itemsJpy + portJpy + items.reduce((a, p) => a + (p.zm_fee || 0), 0);
      if (totalJpy === 0) { result[s.id] = { delta: 0, pct: 0, totalJpy: 0 }; return; }
      const topupsJpy = state.topups
        .filter(t => t.shipment === s.id)
        .reduce((a, t) => a + (t.jpy || 0), 0);
      const delta = topupsJpy - totalJpy;
      const pct = Math.round(topupsJpy / totalJpy * 100);
      result[s.id] = { delta, pct, totalJpy };
    });
    return result;
  }, [shipments, purchases, state.topups]);

  return (
    <div className="page">
      <div className="page-head">
        <div>
          <h1 className="page-title">Commandes</h1>
          <div className="page-sub">Colis internationaux Mercari → ZenMarket → France</div>
        </div>
        <div className="page-actions">
          <button className="btn btn-primary" onClick={() => setNewOpen(true)}><Icon name="plus" /> Nouvelle commande</button>
        </div>
      </div>

      <div className="toolbar">
        <Tabs value={filter} onChange={setFilter} options={[
          { value: "all", label: `Toutes (${shipments.length})` },
          { value: "active", label: `En cours (${shipments.filter(s => s.status !== "delivered").length})` },
          { value: "to_receive", label: `A repartir (${shipments.filter(s => _needsReception(s, purchases)).length})` },
          { value: "delivered", label: `Livrees (${shipments.filter(s => s.status === "delivered").length})` },
        ]} />
        <div className="toolbar-spacer"></div>
        <div className="search">
          <Icon name="search" />
          <input placeholder="Rechercher une commande..." />
        </div>
        <button className="btn btn-sm" onClick={() => window.location.href = "/api/shipments/export.csv"}><Icon name="download" /> Export CSV</button>
      </div>

      <div className="card">
        <table className="tbl">
          <thead>
            <tr>
              <th style={{ width: 60 }}>#</th>
              <th>Statut</th>
              <th>Stock</th>
              <th>Méthode</th>
              <th>Date</th>
              <th className="right">Achats</th>
              <th className="right">Poids</th>
              <th className="right">Port intl</th>
              <th className="right">Taux</th>
              <th className="right">Total JPY</th>
              <th className="right">Recharg.</th>
              <th className="right">Total EUR</th>
              <th style={{ width: 30 }}></th>
            </tr>
          </thead>
          <tbody>
            {filtered.map(s => {
              const items = purchases.filter(p => p.ship === s.id);
              const cur = s.currency || "JPY";
              const sym = cur === "EUR" ? "€" : cur === "USD" ? "$" : "¥";
              let totalJpy, totalEur;
              if (cur === "JPY") {
                const itemsJpy = items.reduce((a, p) => a + (p.jpy || 0) + (p.port_jp || 0), 0);
                const rateInfo = _getEffectiveRate(s, state.topups, state.currentRate);
                totalJpy = itemsJpy + (s.port_jpy || 0) + items.reduce((a, p) => a + (p.zm_fee || 0), 0) + Math.round((s.duty_eur || 0) * rateInfo.rate);
                totalEur = totalJpy / rateInfo.rate;
              } else {
                totalJpy = null;
                totalEur = items.reduce((a, p) => a + (p.eur || 0), 0) + (s.port_eur || 0) + (s.duty_eur || 0) + (s.platform_fee_eur || 0);
              }
              const rateInfo = _getEffectiveRate(s, state.topups, state.currentRate);
              const combo = _getStockStatus(s, purchases);
              return (
                <tr key={s.id} className="clickable" onClick={() => setOpenId(s.id)}>
                  <td className="mono">{s.name || `#${ordinals[s.id]}`}</td>
                  <td onClick={e => e.stopPropagation()}>
                    <div style={{ position: "relative", display: "inline-block" }} title="Cliquer pour changer le statut">
                      <StatusPill status={s.status} />
                      <select
                        style={{ position: "absolute", inset: 0, opacity: 0, cursor: "pointer", width: "100%", height: "100%", border: 0 }}
                        value={s.status || ""}
                        onChange={e => actions.updateShipmentStatus(s.id, e.target.value)}
                      >
                        {_STATUS_OPTIONS.map(o => (
                          <option key={o.v} value={o.v}>{o.label}</option>
                        ))}
                      </select>
                    </div>
                  </td>
                  <td><span className={`tag tag-${combo.color}`} style={{ fontSize: 10.5 }}>{combo.label}</span></td>
                  <td>
                    {s.method}
                    {s.source && s.source !== s.method && (
                      <span style={{ fontSize: 11, color: "var(--ink-3)", marginLeft: 6 }}>· {s.source}</span>
                    )}
                  </td>
                  <td className="mono muted">{fmtDate(s.date)}</td>
                  <td className="right num">{items.length}</td>
                  <td className="right num muted">{s.weight ? `${s.weight} g` : "—"}</td>
                  <td className="right num">
                    {cur === "JPY"
                      ? `¥${fmtJPY(s.port_jpy || 0)}`
                      : `${sym}${fmtEUR(s.port_eur || 0, { decimals: 2 })}`}
                  </td>
                  <td className="right num muted">{cur === "JPY" ? `${rateInfo.rate.toFixed(2)}${rateInfo.source === "estimation" ? " *" : ""}` : "—"}</td>
                  <td className="right num">{totalJpy != null ? `¥${fmtJPY(totalJpy)}` : "—"}</td>
                  <td className="right">
                    {(() => {
                      // CMD1-6 : valeurs figees Excel (RECONCILIATION). Override le calcul live qui compte de travers.
                      const ov = RECON_OVERRIDE[s.id];
                      if (ov) {
                        const pct = Math.round(ov.paidJpy / ov.totalJpy * 100);
                        const ad = Math.abs(ov.deltaJpy);
                        const color = ad < 5000 ? "pos" : ad < 16000 ? "warn" : "neg";
                        const title = `Excel : paye ¥${fmtJPY(ov.paidJpy)} (topups+douane) / total ¥${fmtJPY(ov.totalJpy)}`;
                        if (ad < 5000) {
                          return <span className="tag tag-pos" style={{ fontSize: 10.5 }} title={title}>✓ {pct}%</span>;
                        }
                        if (ov.deltaJpy < 0) {
                          return <span className={`tag tag-${color}`} style={{ fontSize: 10.5 }} title={title}>⚠ -¥{fmtJPY(ad)}</span>;
                        }
                        return <span className={`tag tag-${color}`} style={{ fontSize: 10.5 }} title={title}>+¥{fmtJPY(ad)} en trop</span>;
                      }
                      const cov = topupCoverage[s.id];
                      if (!cov || cov.totalJpy === 0) return <span className="muted">—</span>;
                      const { delta, pct } = cov;
                      if (Math.abs(delta) <= 50) {
                        return <span className="tag tag-pos" style={{ fontSize: 10.5 }}>✓ {pct}%</span>;
                      }
                      if (delta < 0) {
                        return <span className="tag tag-warn" style={{ fontSize: 10.5 }}>⚠ -¥{fmtJPY(Math.abs(delta))}</span>;
                      }
                      return <span className="tag tag-neg" style={{ fontSize: 10.5 }}>+¥{fmtJPY(delta)} en trop</span>;
                    })()}
                  </td>
                  <td className="right num"><Money eur={totalEur} decimals={0} /></td>
                  <td><Icon name="chev" /></td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>

      {openId && shipments.find(s => s.id === openId) && (
        <ShipmentDrawer
          shipment={shipments.find(s => s.id === openId)}
          displayId={ordinals[openId]}
          purchases={purchases.filter(p => p.ship === openId)}
          categories={categories}
          topups={state.topups}
          currentRate={state.currentRate}
          actions={actions}
          onClose={() => setOpenId(null)}
          onReceive={(distrib) => actions.receiveShipment(openId, distrib)}
        />
      )}

      {newOpen && (
        <NewShipmentDrawer
          categories={seriesCats}
          actions={actions}
          onClose={() => setNewOpen(false)}
          onCreate={(shipment, items) => actions.addShipment(shipment, items)}
        />
      )}
    </div>
  );
};

const ShipmentDrawer = ({ shipment, displayId, purchases, categories, topups, currentRate, actions, onClose, onReceive }) => {
  const rateInfo = _getEffectiveRate(shipment, topups || [], currentRate);
  const [mode, setMode] = useState("view"); // view | receive
  // distribution: { [purchaseId]: { target, sealedName?, sealedQty? } }
  // FIX 4 : on stocke des objets pour pouvoir porter sealedName et sealedQty
  const initDist = useMemo(() => {
    const d = {};
    purchases.forEach(p => {
      // sensible default : qty > 1 → boosters NP, sinon scellé
      d[p.id] = { target: p.qty > 1 ? "np" : "sealed" };
    });
    return d;
  }, [purchases]);
  const [distrib, setDistrib] = useState(initDist);
  // change uniquement la cible en preservant les champs sealed si deja remplis
  const setTarget = (pid, target) => setDistrib(d => ({
    ...d,
    [pid]: { ...(d[pid] || {}), target },
  }));
  // met a jour un champ sealed (sealedName ou sealedQty) sans toucher target
  const setSealedField = (pid, field, value) => setDistrib(d => ({
    ...d,
    [pid]: { ...(d[pid] || {}), [field]: value },
  }));
  // catOverrides : surcharge de category_id avant la reception (si Vincent corrige la collection)
  const [catOverrides, setCatOverrides] = useState({});
  const setCatOverride = (pid, catId) => setCatOverrides(o => ({ ...o, [pid]: Number(catId) }));
  // qtyOverrides : surcharge de quantity pour la distribution stock (ex: 1 BOX -> 30 boosters)
  const [qtyOverrides, setQtyOverrides] = useState({});
  const setQtyOverride = (pid, q) => setQtyOverrides(o => ({ ...o, [pid]: Number(q) }));
  // split : decompose un lot en plusieurs sous-items avant reception
  const [splitOpen, setSplitOpen] = useState({});
  const [splitItems, setSplitItems] = useState({});
  // anti double-click : desactive le bouton pendant le submit split
  const [splitSubmitting, setSplitSubmitting] = useState(false);
  const toggleSplit = (pid) => setSplitOpen(s => ({ ...s, [pid]: !s[pid] }));
  const updateSplitItem = (pid, idx, key, val) => setSplitItems(s => {
    const arr = [...(s[pid] || [{qty: 1, priceMode: "pct", priceValue: 50, target: "np", category_id: null, sealed_name: null}, {qty: 1, priceMode: "pct", priceValue: 50, target: "np", category_id: null, sealed_name: null}])];
    arr[idx] = { ...arr[idx], [key]: val };
    return { ...s, [pid]: arr };
  });
  const addSplitItem = (pid, defaultCat) => setSplitItems(s => {
    const arr = [...(s[pid] || []), {qty: 1, priceMode: "pct", priceValue: 0, target: "np", category_id: defaultCat || null, sealed_name: null}];
    return { ...s, [pid]: arr };
  });
  const removeSplitItem = (pid, idx) => setSplitItems(s => {
    const arr = (s[pid] || []).filter((_, i) => i !== idx);
    return { ...s, [pid]: arr };
  });

  // currency-aware : commandes manuelles (EUR/USD) n'ont pas de yens ni de taux
  const cur = shipment.currency || "JPY";
  const isJpy = cur === "JPY";
  const curSym = cur === "EUR" ? "€" : cur === "USD" ? "$" : "¥";
  const itemsJpy = purchases.reduce((a, p) => a + (p.jpy || 0) + (p.port_jp || 0), 0);
  const itemsEur = purchases.reduce((a, p) => a + (p.eur || 0), 0);
  // lecture robuste : port_jpy prioritaire, sinon intl_shipping_jpy (fix retrocompat shipments 28-30)
  const portJpy = shipment.port_jpy || shipment.intl_shipping_jpy || 0;
  // commission ZM reelle par achat (uniquement pour les colis JPY)
  const commissionJpy = isJpy ? purchases.reduce((a, p) => a + (p.zm_fee || 0), 0) : 0;
  const safeRate = rateInfo.rate > 0 && isFinite(rateInfo.rate) ? rateInfo.rate : 170;
  const dutyJpy = isJpy ? Math.round((shipment.duty_eur || 0) * safeRate) : 0;
  const totalJpy = itemsJpy + portJpy + commissionJpy + dutyJpy;
  const totalEur = isJpy
    ? (totalJpy / safeRate)
    : itemsEur + (shipment.port_eur || 0) + (shipment.duty_eur || 0) + (shipment.platform_fee_eur || 0);
  const [confirming, setConfirming] = useState(false);
  const [deleting, setDeleting] = useState(false);
  const confirmDelete = () => {
    setDeleting(true);
    // actions.deleteShipment fait l'optimistic update + DELETE backend + resync
    // -> on attend la fin pour fermer le drawer (overlay floue visible pendant ce temps)
    Promise.resolve(actions.deleteShipment(shipment.id))
      .then(() => onClose())
      .catch(() => setDeleting(false));
  };

  return (
    <React.Fragment>
    {deleting && (
      <div className="loading-overlay">
        <div className="loading-spinner"></div>
        <div className="loading-msg">Suppression du colis...</div>
      </div>
    )}
    <Drawer open onClose={deleting ? () => {} : onClose} wide
      title={shipment.name || `Commande #${displayId || shipment.id}`}
      sub={`${shipment.method} · ${fmtDate(shipment.date)}`}
      footer={
        mode === "view" ? (
          confirming ? (
            <React.Fragment>
              <span style={{ color: "var(--neg)", fontSize: 13, marginRight: "auto" }}>
                Supprimer ce colis ? Les {purchases.length} achat{purchases.length > 1 ? "s" : ""} seront détachés (mais conservés), idem pour les rechargements liés.
              </span>
              <button className="btn" onClick={() => setConfirming(false)}>Annuler</button>
              <button className="btn btn-danger" disabled={deleting} onClick={confirmDelete}>
                <Icon name="trash" /> {deleting ? "Suppression..." : "Oui, supprimer"}
              </button>
            </React.Fragment>
          ) : (
          <React.Fragment>
            <button className="btn btn-danger" onClick={() => setConfirming(true)}>
              <Icon name="trash" /> Supprimer
            </button>
            <div style={{ flex: 1 }}></div>
            <button className="btn" onClick={onClose}>Fermer</button>
            {shipment.status !== "delivered" && (
              <button className="btn btn-primary" onClick={() => setMode("receive")}>
                <Icon name="check" /> Marquer reçue & répartir
              </button>
            )}
          </React.Fragment>
          )
        ) : (
          <React.Fragment>
            <button className="btn" onClick={() => setMode("view")}>Retour</button>
            <div style={{ flex: 1 }}></div>
            <button className="btn btn-primary" onClick={() => {
              const overrides = Object.entries(catOverrides).filter(([pid, cid]) => {
                const p = purchases.find(x => x.id === Number(pid));
                return p && cid && cid !== p.cat;
              });
              // applique tout de suite la cat dans le state local pour que la mutation use la bonne cat
              overrides.forEach(([pid, cid]) => {
                actions.patchPurchaseCategory && actions.patchPurchaseCategory(Number(pid), cid);
              });
              // FIX 4 : construit la distribution avec target + sealedName/sealedQty si scelle
              const distFull = {};
              purchases.forEach(p => {
                const d = distrib[p.id] || { target: "np" };
                const entry = { target: d.target || "np" };
                if (qtyOverrides[p.id] && qtyOverrides[p.id] > 0) {
                  entry.quantity = qtyOverrides[p.id];
                }
                if (d.target === "sealed") {
                  if (d.sealedName) entry.sealedName = d.sealedName;
                  // sealedQty override la quantite stock si fourni
                  if (d.sealedQty && d.sealedQty > 0) entry.quantity = d.sealedQty;
                }
                distFull[p.id] = entry;
              });
              // lance la reception (mutation optimiste + fetch backend asynchrone)
              onReceive(distFull);
              // patche les categories en backend en arriere-plan (le receive ne depend pas du backend pour la cat car deja mutee localement)
              overrides.forEach(([pid, cid]) => {
                window._apiFetch?.("PATCH", `/api/purchases/${pid}`, { category_id: cid })
                  .catch(e => console.warn("LDG: patch category failed", e));
              });
              // ferme le drawer immediatement
              onClose();
            }}>
              <Icon name="check" /> Confirmer la répartition
            </button>
          </React.Fragment>
        )
      }
    >
      {mode === "view" && (
        <React.Fragment>
          {shipment.status !== "delivered" && purchases.length > 0 && (
            <div style={{ marginBottom: 16, padding: 14, background: "var(--bg-sunk)", color: "var(--ink-2)", borderRadius: 8, fontSize: 13, border: "1px solid var(--line)", display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12 }}>
              <div>
                <b>⚠ Stock pas encore reparti.</b> Cette commande contient {purchases.length} achat{purchases.length > 1 ? "s" : ""} non distribues vers le stock (NP / NH / Hit / Scelle).
              </div>
              <button className="btn btn-sm btn-primary" onClick={() => setMode("receive")}>
                <Icon name="check" /> Marquer recue & repartir
              </button>
            </div>
          )}
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16, marginBottom: 16 }}>
            <div className="card card-pad">
              <div className="kpi-label">Statut</div>
              <div style={{ marginTop: 8, display: "flex", alignItems: "center", gap: 8 }}>
                <StatusPill status={shipment.status} />
                <select
                  className="select"
                  style={{ fontSize: 11, padding: "2px 6px", height: 26, maxWidth: 130 }}
                  value={shipment.status || ""}
                  onChange={e => actions.updateShipmentStatus(shipment.id, e.target.value)}
                >
                  <option value="pending">pending</option>
                  <option value="created">created</option>
                  <option value="ordered">ordered</option>
                  <option value="in_transit">in_transit</option>
                  <option value="received">received</option>
                  <option value="delivered">delivered</option>
                  <option value="cancelled">cancelled</option>
                </select>
              </div>
              {shipment.notes && (
                <div style={{ marginTop: 14, padding: 10, background: "var(--neg-tint)", color: "var(--neg)", borderRadius: 6, fontSize: 12.5 }}>
                  ⚠ {shipment.notes}
                </div>
              )}
              <ShipmentNoteEditor shipment={shipment} actions={actions} />
            </div>
            <div className="card card-pad">
              <div className="kpi-label">Cout total</div>
              <div className="kpi-value" style={{ marginTop: 6 }}>
                {fmtEUR(totalEur, { decimals: 2 })} <span className="unit">€</span>
              </div>
              {isJpy && (
                <div style={{ fontSize: 11.5, color: "var(--ink-3)", marginTop: 6, fontFamily: "var(--font-mono)", lineHeight: 1.5 }}>
                  items {fmtEUR(itemsJpy / safeRate)}€ · port {fmtEUR(portJpy / safeRate)}€{!shipment.source ? ` · comm. ${fmtEUR(commissionJpy / safeRate)}€` : ""}{(shipment.duty_eur || 0) > 0 ? ` · douane ${fmtEUR(shipment.duty_eur)}€` : ""}
                </div>
              )}
              {!isJpy && shipment.source && (
                <div style={{ fontSize: 11.5, color: "var(--ink-3)", marginTop: 2, fontFamily: "var(--font-mono)" }}>
                  {shipment.source} · {cur}
                </div>
              )}
            </div>
          </div>

          {/* Bandeau statut taux — uniquement pour les colis JPY (Mercari/ZenMarket) */}
          {isJpy && (
            <div className="card card-pad" style={{ marginBottom: 18, borderLeft: `3px solid var(--${rateInfo.color === "warn" ? "warn" : "pos"})` }}>
              <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12, flexWrap: "wrap" }}>
                <div>
                  <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                    <span className={`tag tag-${rateInfo.color}`}>{rateInfo.label}</span>
                    <span className="mono" style={{ fontSize: 13, fontWeight: 500 }}>{rateInfo.rate.toFixed(2)} ¥/€</span>
                  </div>
                  <div style={{ fontSize: 11.5, color: "var(--ink-3)", marginTop: 6 }}>
                    {rateInfo.source === "topups" && "Taux pondere calcule depuis les rechargements lies a ce colis."}
                    {rateInfo.source === "manual" && "Taux saisi manuellement pour cette commande."}
                    {rateInfo.source === "estimation" && "Aucun rechargement lie - estimation au dernier taux connu du systeme. Lie un rechargement pour confirmer le cout reel."}
                  </div>
                </div>
              </div>
            </div>
          )}

          {/* Bloc rechargement : statut + validation manuelle (JPY uniquement) */}
          {isJpy && (() => {
            // purchases dans ce scope = achats du colis courant (deja filtres par p.ship === shipment.id)
            const rechaStatusFull = _getRechaStatus(shipment, topups || [], purchases);
            return (
              <div className="card card-pad" style={{ marginBottom: 18, display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 14, flexWrap: "wrap" }}>
                <div>
                  <div style={{ fontSize: 11, color: "var(--ink-3)", textTransform: "uppercase", letterSpacing: "0.05em", marginBottom: 6 }}>Rechargement ZenMarket</div>
                  <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                    <span className={`tag tag-${rechaStatusFull.color}`}>{rechaStatusFull.label}</span>
                    {rechaStatusFull.pct !== null && rechaStatusFull.pct !== undefined && (
                      <span className="mono" style={{ fontSize: 11.5, color: "var(--ink-3)" }}>{rechaStatusFull.pct}% couvert</span>
                    )}
                    {rechaStatusFull.deltaJpy != null && rechaStatusFull.deltaJpy !== 0 && <span style={{ fontSize: 11, color: "var(--ink-3)", marginLeft: 6 }}>{(rechaStatusFull.deltaJpy > 0 ? "+" : "") + fmtJPY(rechaStatusFull.deltaJpy) + "¥"}</span>}
                  </div>
                </div>
                <div style={{ display: "flex", flexDirection: "column", alignItems: "flex-end", gap: 6 }}>
                  <label style={{ display: "flex", alignItems: "center", gap: 8, cursor: "pointer", userSelect: "none" }}>
                    <input
                      type="checkbox"
                      checked={!!shipment.recharge_confirmed}
                      onChange={e => actions.updateShipmentRechargeConfirmed(shipment.id, e.target.checked)}
                      style={{ width: 15, height: 15, accentColor: "var(--accent)", cursor: "pointer" }}
                    />
                    <span style={{ fontSize: 12.5, fontWeight: 500 }}>Valider le rechargement (manuel)</span>
                  </label>
                  <div style={{ fontSize: 11, color: "var(--ink-3)", textAlign: "right", maxWidth: 240, lineHeight: 1.4 }}>
                    Force le statut Confirme meme si la couverture n'est pas 95-100% (note le detail dans les notes).
                  </div>
                </div>
              </div>
            );
          })()}

          <dl className="kv" style={{ marginBottom: 18 }}>
            <dt>Méthode</dt><dd>{shipment.method}{shipment.source && shipment.source !== shipment.method ? ` · ${shipment.source}` : ""}</dd>
            <dt>Poids</dt><dd>{shipment.weight ? `${shipment.weight} g` : "—"}</dd>
            <dt>Port {isJpy ? "international" : ""}</dt>
            <dd>{isJpy ? `¥${fmtJPY(portJpy)}` : `${curSym}${fmtEUR(shipment.port_eur || 0, { decimals: 2 })}`}</dd>
            {isJpy && commissionJpy > 0 && !shipment.source && (<React.Fragment>
              <dt>Commission Mercari</dt>
              <dd>¥{fmtJPY(commissionJpy)} <span style={{ fontSize: 11, color: "var(--ink-3)" }}>({purchases.length} × {fmtJPY(commissionJpy / Math.max(purchases.length, 1))}¥)</span></dd>
            </React.Fragment>)}
            {isJpy && (<React.Fragment>
              <dt>Taux JPY/EUR</dt>
              <dd>{rateInfo.rate.toFixed(2)} {rateInfo.source === "estimation" && <span style={{ fontSize: 11, color: "var(--warn)" }}>(estimation)</span>}</dd>
            </React.Fragment>)}
            {!isJpy && (shipment.platform_fee_eur || 0) > 0 && (<React.Fragment>
              <dt>Commission plateforme</dt><dd>{fmtEUR(shipment.platform_fee_eur)} €</dd>
            </React.Fragment>)}
            <dt>Douane</dt>
            <dd>
              <input
                type="number"
                step="0.01"
                min="0"
                defaultValue={shipment.duty_eur || 0}
                onBlur={e => {
                  const newVal = Number(e.target.value) || 0;
                  if (newVal !== (shipment.duty_eur || 0)) {
                    actions.updateShipmentDuty(shipment.id, newVal);
                  }
                }}
                onKeyDown={e => { if (e.key === "Enter") e.target.blur(); }}
                style={{
                  background: "transparent",
                  border: "1px solid var(--line)",
                  borderRadius: 4,
                  padding: "2px 6px",
                  width: 90,
                  fontSize: 13,
                  fontFamily: "var(--font-mono)",
                  color: "var(--ink)",
                  textAlign: "right",
                }}
              /> €
            </dd>
            <dt>Date</dt><dd>{fmtDate(shipment.date)}</dd>
          </dl>

          <div className="subhead">
            <h3>Achats ({purchases.length})</h3>
            <span className="mono" style={{ fontSize: 12, color: "var(--ink-3)" }}>
              {isJpy
                ? `¥${fmtJPY(itemsJpy)} items · ¥${fmtJPY(portJpy)} port · ¥${fmtJPY(commissionJpy)} commission`
                : `${fmtEUR(itemsEur)} € items${(shipment.port_eur || 0) > 0 ? ` · ${fmtEUR(shipment.port_eur)} € port` : ""}`}
            </span>
          </div>

          <div style={{ border: "1px solid var(--line)", borderRadius: 8, overflow: "hidden" }}>
            <table className="tbl">
              <thead>
                <tr>
                  <th>Titre</th><th>Série</th><th className="right">Qty</th>
                  {isJpy && <th className="right">JPY</th>}
                  <th className="right">EUR final</th>
                </tr>
              </thead>
              <tbody>
                {purchases.map(p => {
                  const cat = categories.find(c => c.id === p.cat);
                  // CMD ZenMarket (source='zenmarket') : lecture directe Excel via p.zm_fee + p.intl_ship
                  // CMD Mercari pur (source=null, ex: CMD7) : prorata via shipment.port_jpy + douane
                  const isExcelDirect = !!shipment.source;
                  const commJpy = p.zm_fee || 0;
                  const intlShare = isExcelDirect
                    ? (p.intl_ship || 0)
                    : (itemsJpy > 0 ? Math.round(portJpy * (p.jpy || 0) / itemsJpy) : 0);
                  const totalDuty = shipment.duty_eur || 0;
                  const dutyShareEur = (!isExcelDirect && itemsJpy > 0 && totalDuty > 0)
                    ? +(totalDuty * (p.jpy || 0) / itemsJpy).toFixed(2)
                    : 0;
                  const fullJpy = (p.jpy || 0) + (p.port_jp || 0) + commJpy + intlShare;
                  const fullEur = p.eur || (rateInfo.rate > 0 ? fullJpy / rateInfo.rate : 0);
                  const fullEurWithDuty = fullEur + dutyShareEur;
                  return (
                    <tr key={p.id}>
                      <td>
                        <div>{p.title}</div>
                        {isJpy && (
                          <div style={{ fontSize: 10.5, color: "var(--ink-3)", fontFamily: "var(--font-mono)", marginTop: 2 }}>
                            ¥{fmtJPY(p.jpy || 0)}{(p.port_jp || 0) > 0 && ` + ¥${fmtJPY(p.port_jp)} port`}{commJpy > 0 && ` + ¥${fmtJPY(commJpy)} comm.`}{intlShare > 0 && ` + ¥${fmtJPY(intlShare)} frais`}{dutyShareEur > 0 ? ` + ${fmtEUR(dutyShareEur, { decimals: 2 })}€ douane` : ""}
                          </div>
                        )}
                      </td>
                      <td><span className="tag">{cat?.short || "—"}</span></td>
                      <td className="right num">{p.qty}</td>
                      {isJpy && <td className="right num muted">¥{fmtJPY(fullJpy)}</td>}
                      <td className="right num">{`${fmtEUR(fullEurWithDuty)} €`}</td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
        </React.Fragment>
      )}

      {mode === "receive" && (
        <React.Fragment>
          <div style={{ background: "var(--accent-tint)", color: "var(--accent)", padding: 12, borderRadius: 8, fontSize: 13, marginBottom: 18 }}>
            Pour chaque achat, choisis où il atterrit. <b>NP</b> = non pesés (à trier plus tard), <b>NH</b> = pesés sans hit, <b>Hit</b> = pesés avec hit, <b>Scellé</b> = display/deck/ETB. Tu pourras repeser plus tard depuis l'écran Stock.
          </div>
          {purchases.map(p => {
            const effectiveCatId = catOverrides[p.id] || p.cat;
            const cat = categories.find(c => c.id === effectiveCatId);
            // FIX 4 : distrib[p.id] est maintenant un objet { target, sealedName?, sealedQty? }
            const distribEntry = distrib[p.id] || { target: "np" };
            const target = distribEntry.target || "np";
            const effectiveQty = qtyOverrides[p.id] || p.qty || 1;
            // CMD ZenMarket : direct depuis Excel (p.zm_fee + p.intl_ship)
            // CMD Mercari pur : prorata shipment.port_jpy
            const isExcelDirect = !!shipment.source;
            const commJpy = p.zm_fee || 0;
            const intlShare = isExcelDirect
              ? (p.intl_ship || 0)
              : (itemsJpy > 0 ? Math.round(portJpy * (p.jpy || 0) / itemsJpy) : 0);
            const fullJpy = (p.jpy || 0) + (p.port_jp || 0) + commJpy + intlShare;
            const fullEur = p.eur || (rateInfo.rate > 0 ? fullJpy / rateInfo.rate : fullJpy / 170);
            // diviseur : sealedQty si scelle et defini, sinon effectiveQty
            const divQty = target === "sealed" && distribEntry.sealedQty > 0 ? distribEntry.sealedQty : Math.max(1, effectiveQty);
            const targets = [
              { v: "np",     label: "NP",     sub: "non pesé" },
              { v: "nh",     label: "NH",     sub: "pesé" },
              { v: "h",      label: "Hit",    sub: "rare" },
              { v: "sealed", label: "Scellé", sub: "unitaire" },
            ];
            return (
              <div key={p.id} className="receive-row-v2">
                <div className="what">
                  {p.title}
                  <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12, flexWrap: "wrap" }}>
                    <small style={{ margin: 0 }}>achat : {p.qty} unité{p.qty > 1 ? "s" : ""} · ¥{fmtJPY(p.jpy + p.port_jp)}</small>
                    {/* Bouton split : a droite de la ligne "achat: X unite ¥prix" */}
                    <button
                      onClick={() => {
                        if (!splitItems[p.id]) {
                          setSplitItems(s => ({ ...s, [p.id]: [
                            {qty: Math.ceil((p.qty || 1) / 2), priceMode: "pct", priceValue: 50, target: "np", category_id: p.cat || null, sealed_name: null},
                            {qty: Math.floor((p.qty || 1) / 2), priceMode: "pct", priceValue: 50, target: "np", category_id: p.cat || null, sealed_name: null},
                          ]}));
                        }
                        toggleSplit(p.id);
                      }}
                      style={{
                        fontSize: 11.5,
                        padding: "4px 10px",
                        background: splitOpen[p.id] ? "var(--accent-tint)" : "transparent",
                        color: splitOpen[p.id] ? "var(--accent)" : "var(--ink-3)",
                        border: `1px dashed ${splitOpen[p.id] ? "var(--accent)" : "var(--line-strong)"}`,
                        borderRadius: 6,
                        cursor: "pointer",
                        fontWeight: splitOpen[p.id] ? 600 : 500,
                        transition: "all 0.15s",
                        display: "inline-flex",
                        alignItems: "center",
                        gap: 6,
                        whiteSpace: "nowrap",
                      }}
                    >
                      {splitOpen[p.id] ? (
                        <React.Fragment>← Fermer le split</React.Fragment>
                      ) : (
                        <React.Fragment>⊞ Plusieurs produits ?</React.Fragment>
                      )}
                    </button>
                  </div>
                  {/* categorie + qty stock : masques en mode scelle ou split ouvert */}
                  {!splitOpen[p.id] && target !== "sealed" && (
                  <div style={{ marginTop: 6, display: "flex", gap: 6, alignItems: "center", flexWrap: "wrap" }}>
                    <NewCatInline
                      categories={categories}
                      value={effectiveCatId || ""}
                      onChange={id => setCatOverride(p.id, id)}
                      actions={actions}
                      selectStyle={{ fontSize: 11.5, padding: "3px 6px", height: 26, maxWidth: 200 }}
                    />
                    <label style={{ fontSize: 11, color: "var(--ink-3)" }}>qty stock :</label>
                    <input
                      className="input mono"
                      type="number"
                      min={1}
                      style={{ fontSize: 11.5, padding: "3px 6px", height: 26, width: 70 }}
                      value={effectiveQty}
                      onChange={e => setQtyOverride(p.id, e.target.value)}
                    />
                  </div>
                  )}
                  {/* FIX 4 : champs nom + quantite quand Scelle est selectionne, masques si split ouvert */}
                  {!splitOpen[p.id] && target === "sealed" && (
                    <div style={{ marginTop: 8, display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }}>
                      <input
                        className="input"
                        type="text"
                        placeholder="Nom (ex: Display 151 scelle)"
                        style={{ fontSize: 12, padding: "4px 8px", height: 28, flex: "1 1 200px", minWidth: 160 }}
                        value={distribEntry.sealedName || ""}
                        onChange={e => setSealedField(p.id, "sealedName", e.target.value)}
                      />
                      <input
                        className="input mono"
                        type="number"
                        min={1}
                        placeholder="Qte"
                        style={{ fontSize: 12, padding: "4px 8px", height: 28, width: 70 }}
                        value={distribEntry.sealedQty || ""}
                        onChange={e => setSealedField(p.id, "sealedQty", Number(e.target.value) || "")}
                      />
                    </div>
                  )}
                </div>
                {/* Boutons NP/NH/Hit/Scelle : a droite, centres verticalement */}
                {!splitOpen[p.id] && (
                <div className="receive-target-v2">
                  {targets.map(t => (
                    <button key={t.v}
                            className={target === t.v ? "active" : ""}
                            onClick={() => setTarget(p.id, t.v)}>
                      <span className="rtl-label">{t.label}</span>
                      <span className="rtl-sub">{t.sub}</span>
                    </button>
                  ))}
                </div>
                )}
                {/* €/u : masque quand split est ouvert */}
                {!splitOpen[p.id] && (
                <div className="mono" style={{ textAlign: "right", fontSize: 12, color: "var(--ink-3)" }}>
                  {(fullEur / divQty).toFixed(2)} €/u
                </div>
                )}
                {splitOpen[p.id] && (() => {
                  const parentPriceEurForSplit = fullEur || 0;
                  const splitItemsList = splitItems[p.id] || [];
                  const totalRatio = splitItemsList.reduce((acc, it) => {
                    const r = it.priceMode === "pct"
                      ? (it.priceValue || 0) / 100
                      : parentPriceEurForSplit > 0 ? (it.priceValue || 0) / parentPriceEurForSplit : 0;
                    return acc + r;
                  }, 0);
                  // validation : renvoie le 1er message d'erreur ou null si OK
                  let splitError = null;
                  if (splitItemsList.length < 2) {
                    splitError = "Au moins 2 sous-items requis";
                  } else if (Math.abs(totalRatio - 1) > 0.01) {
                    splitError = `Total des prix = ${(totalRatio * 100).toFixed(0)}% (attendu 100%)`;
                  } else if (splitItemsList.some(i => (i.qty || 0) < 1)) {
                    splitError = "Quantite manquante sur un sous-item";
                  } else if (splitItemsList.some(i => i.target !== "sealed" && !i.category_id)) {
                    splitError = "Choisis une collection pour chaque sous-item non-scelle";
                  } else if (splitItemsList.some(i => i.target === "sealed" && !(i.sealed_name || "").trim())) {
                    splitError = "Donne un nom aux sous-items scelles";
                  }
                  const splitValid = !splitError;
                  return (
                  <div style={{
                    gridColumn: "1 / -1",
                    marginTop: 12,
                    padding: 16,
                    background: "var(--surface-2, var(--bg-sunk))",
                    border: "1px solid var(--accent)",
                    borderRadius: 8,
                    boxShadow: "0 1px 3px rgba(0,0,0,0.08)",
                  }}>
                    <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 14 }}>
                      <div>
                        <div style={{ fontSize: 13, fontWeight: 600, color: "var(--ink)" }}>Decomposer ce lot en sous-items</div>
                        <div style={{ fontSize: 11.5, color: "var(--ink-3)", marginTop: 2 }}>
                          Decompose le lot en plusieurs items. Tu pourras les repartir individuellement apres dans la commande.
                        </div>
                      </div>
                      <div style={{ fontSize: 12, fontFamily: "var(--font-mono)", color: Math.abs(totalRatio - 1) <= 0.01 ? "var(--pos)" : "var(--neg)", fontWeight: 600 }}>
                        Total : {((totalRatio || 0) * 100).toFixed(0)}% {Math.abs(totalRatio - 1) <= 0.01 && "✓"}
                      </div>
                    </div>

                    {/* Header des colonnes */}
                    <div style={{ display: "grid", gridTemplateColumns: "160px 80px 1fr 30px", gap: 8, fontSize: 10.5, fontWeight: 600, color: "var(--ink-3)", textTransform: "uppercase", letterSpacing: "0.05em", marginBottom: 6, padding: "0 10px" }}>
                      <div>Prix</div>
                      <div style={{ textAlign: "center" }}>Quantite</div>
                      <div>Stock</div>
                      <div></div>
                    </div>

                    {/* Lignes sous-items */}
                    {(splitItems[p.id] || []).map((it, idx) => {
                      return (
                        <div key={idx} style={{ display: "grid", gridTemplateColumns: "160px 80px 1fr 30px", gap: 8, alignItems: "center", marginBottom: 8, padding: "10px", background: "var(--surface)", borderRadius: 6, border: "1px solid var(--line)" }}>

                          {/* PRIX avec toggle €/% */}
                          <div style={{ display: "flex", border: "1px solid var(--line)", borderRadius: 6, overflow: "hidden", height: 30 }}>
                            <input className="input mono" type="number" step="0.01" min={0}
                              style={{ flex: 1, fontSize: 12.5, padding: "0 8px", border: "none", borderRadius: 0, textAlign: "right" }}
                              value={it.priceValue || ""}
                              onChange={e => updateSplitItem(p.id, idx, "priceValue", Number(e.target.value) || 0)}
                            />
                            <button type="button"
                              onClick={() => updateSplitItem(p.id, idx, "priceMode", it.priceMode === "pct" ? "eur" : "pct")}
                              style={{ background: "var(--bg-sunk)", border: "none", borderLeft: "1px solid var(--line)", padding: "0 10px", fontSize: 12, cursor: "pointer", fontWeight: 600, color: "var(--ink-2)" }}
                              title="Basculer entre % et €"
                            >
                              {it.priceMode === "pct" ? "%" : "€"}
                            </button>
                          </div>

                          {/* QTY */}
                          <input className="input mono" type="number" min={1}
                            style={{ fontSize: 12.5, padding: "5px 6px", height: 30, textAlign: "center" }}
                            value={it.qty || ""}
                            onChange={e => updateSplitItem(p.id, idx, "qty", Number(e.target.value) || 1)}
                          />

                          {/* STOCK : target + collection/sealed_name conditionnel */}
                          <div style={{ display: "grid", gridTemplateColumns: "120px 1fr", gap: 6 }}>
                            <select className="select"
                              style={{ fontSize: 12, padding: "5px 6px", height: 30 }}
                              value={it.target || "np"}
                              onChange={e => updateSplitItem(p.id, idx, "target", e.target.value)}
                            >
                              <option value="np">NP — non pese</option>
                              <option value="nh">NH — pese sans hit</option>
                              <option value="h">Hit — pese avec hit</option>
                              <option value="sealed">Scelle</option>
                            </select>
                            {it.target === "sealed" ? (
                              <input className="input" type="text" placeholder="Nom du scelle (ex: Display SV3)"
                                style={{ fontSize: 12, padding: "5px 8px", height: 30 }}
                                value={it.sealed_name || ""}
                                onChange={e => updateSplitItem(p.id, idx, "sealed_name", e.target.value)}
                              />
                            ) : (
                              <NewCatInline
                                categories={categories}
                                value={it.category_id || ""}
                                onChange={id => updateSplitItem(p.id, idx, "category_id", Number(id) || null)}
                                actions={actions}
                                selectStyle={{ fontSize: 12, padding: "5px 6px", height: 30 }}
                                placeholder="— Choisir la collection —"
                              />
                            )}
                          </div>

                          {/* DELETE */}
                          <button onClick={() => removeSplitItem(p.id, idx)}
                            style={{ background: "transparent", border: "none", color: "var(--neg)", cursor: "pointer", fontSize: 18, lineHeight: 1, padding: 0 }}
                            title="Supprimer ce sous-item"
                          >×</button>
                        </div>
                      );
                    })}

                    {/* Actions */}
                    <div style={{ marginTop: 12, paddingTop: 12, borderTop: "1px solid var(--line)" }}>
                      {splitError && (
                        <div style={{ fontSize: 11.5, color: "var(--neg)", marginBottom: 10, textAlign: "right", fontWeight: 500 }}>
                          {splitError}
                        </div>
                      )}
                      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
                        <button onClick={() => addSplitItem(p.id, p.cat || null)}
                          style={{ fontSize: 12, padding: "6px 12px", background: "var(--bg-sunk)", border: "1px dashed var(--line-strong)", borderRadius: 6, cursor: "pointer", color: "var(--accent)", fontWeight: 500 }}>
                          + Ajouter un sous-item
                        </button>
                        <div style={{ display: "flex", gap: 8 }}>
                          <button onClick={() => toggleSplit(p.id)}
                            style={{ fontSize: 12, padding: "6px 14px", background: "transparent", border: "1px solid var(--line)", borderRadius: 6, cursor: "pointer" }}>
                            Annuler
                          </button>
                          <button
                            disabled={!splitValid || splitSubmitting}
                            onClick={async () => {
                              if (!splitValid || splitSubmitting) return;
                              const its = splitItemsList.map(it => {
                                const ratio = it.priceMode === "pct"
                                  ? (it.priceValue || 0) / 100
                                  : parentPriceEurForSplit > 0 ? (it.priceValue || 0) / parentPriceEurForSplit : 0;
                                const catShort = categories.find(c => c.id === it.category_id)?.short || "";
                                const targetLabel = {np: "NP", nh: "NH", h: "Hit", sealed: "scelle"}[it.target] || "";
                                const title = it.target === "sealed"
                                  ? (it.sealed_name || "scelle")
                                  : `${it.qty}x ${catShort} ${targetLabel}`.trim();
                                return {
                                  title,
                                  qty: it.qty || 1,
                                  ratio,
                                  target: it.target,
                                  category_id: it.category_id,
                                  sealed_name: it.sealed_name,
                                };
                              });
                              try {
                                setSplitSubmitting(true);
                                const result = await actions.splitPurchase(p.id, its);
                                // pre-remplir distrib state pour chaque nouvel enfant
                                if (result && result.new_ids) {
                                  const newDistrib = {};
                                  result.new_ids.forEach((newId, idx) => {
                                    const it = its[idx];
                                    const entry = { target: it.target || "np" };
                                    if (it.target === "sealed") {
                                      entry.sealedName = it.sealed_name || it.title;
                                      entry.sealedQty = it.qty || 1;
                                    }
                                    newDistrib[newId] = entry;
                                  });
                                  setDistrib(d => ({ ...d, ...newDistrib }));
                                }
                                toggleSplit(p.id);
                              } catch (e) {
                                alert("Erreur split: " + e.message);
                              } finally {
                                setSplitSubmitting(false);
                              }
                            }}
                            className="btn btn-primary"
                            style={{
                              fontSize: 12.5,
                              padding: "6px 16px",
                              fontWeight: 600,
                              opacity: (splitValid && !splitSubmitting) ? 1 : 0.4,
                              cursor: (splitValid && !splitSubmitting) ? "pointer" : "not-allowed",
                            }}
                          >
                            {splitSubmitting ? "Decomposition..." : "✓ Decomposer le lot"}
                          </button>
                        </div>
                      </div>
                    </div>
                  </div>
                  );
                })()}
              </div>
            );
          })}
        </React.Fragment>
      )}
    </Drawer>
    </React.Fragment>
  );
};

// ---------- Editeur de note inline (autosave on blur) ----------
const ShipmentNoteEditor = ({ shipment, actions }) => {
  const [draft, setDraft] = useState(shipment.notes || "");
  const [focused, setFocused] = useState(false);

  // Resync si on ouvre le drawer d'un autre colis
  useEffect(() => { setDraft(shipment.notes || ""); }, [shipment.id, shipment.notes]);

  const save = () => {
    const cleaned = (draft || "").trim();
    const current = (shipment.notes || "").trim();
    if (cleaned === current) return;
    actions.updateShipmentNotes(shipment.id, cleaned || null);
  };

  return (
    <div style={{ marginTop: 14 }}>
      <div style={{ fontSize: 11, color: "var(--ink-3)", textTransform: "uppercase", letterSpacing: "0.05em", marginBottom: 6, display: "flex", justifyContent: "space-between", alignItems: "center" }}>
        <span>Note</span>
        {focused && <span style={{ color: "var(--ink-4)", textTransform: "none", letterSpacing: 0, fontSize: 10 }}>sauvegarde auto</span>}
      </div>
      <textarea
        className="input"
        rows={2}
        value={draft}
        placeholder="Ajouter une note (livraison, anomalie, suivi…)"
        onChange={e => setDraft(e.target.value)}
        onFocus={() => setFocused(true)}
        onBlur={() => { setFocused(false); save(); }}
        style={{ width: "100%", resize: "vertical", fontSize: 13, padding: 8, fontFamily: "inherit", lineHeight: 1.4 }}
      />
    </div>
  );
};

// =============================================================
// Nouvelle commande — drawer manuel hors workflow Mercari
// =============================================================
const ORDER_SOURCES = [
  { value: "eBay",             needsCustom: false },
  { value: "Vinted",           needsCustom: false },
  { value: "Cardmarket",       needsCustom: false },
  { value: "Magasin physique", needsCustom: false },
  { value: "Autre",            needsCustom: true  },
];
const CURRENCIES = [
  { value: "EUR", sym: "€", defaultRate: 1.00 },
  { value: "JPY", sym: "¥", defaultRate: 170.00 },
  { value: "USD", sym: "$", defaultRate: 1.07 },
];

const _todayISO = () => new Date().toISOString().slice(0, 10);
const _newLineId = (() => { let n = 0; return () => ++n; })();

const NewShipmentDrawer = ({ categories, onClose, onCreate, actions }) => {
  const [source, setSource] = useState("eBay");
  const [sourceCustom, setSourceCustom] = useState("");
  const [date, setDate] = useState(_todayISO());
  const [currency, setCurrency] = useState("EUR");
  const [rate, setRate] = useState(1.00); // 1 EUR = rate × devise
  const [items, setItems] = useState([
    { lid: _newLineId(), title: "", cat: categories[0]?.id || 1, qty: 1, unit: "" },
  ]);
  const [shipping, setShipping] = useState("");
  const [duty, setDuty] = useState("");
  const [platformFee, setPlatformFee] = useState("");
  const [notes, setNotes] = useState("");
  const [submitting, setSubmitting] = useState(false);

  const curObj = CURRENCIES.find(c => c.value === currency);
  const sym = curObj.sym;

  // reinit taux par defaut quand la devise change
  useEffect(() => {
    const c = CURRENCIES.find(x => x.value === currency);
    if (c) setRate(c.defaultRate);
  }, [currency]);

  // convertit un montant devise → EUR
  const toEUR = (amount) => {
    if (currency === "EUR") return Number(amount) || 0;
    return (Number(amount) || 0) / (Number(rate) || 1);
  };

  const addLine = () => setItems(is => [...is, { lid: _newLineId(), title: "", cat: categories[0]?.id || 1, qty: 1, unit: "" }]);
  const removeLine = (lid) => setItems(is => is.length > 1 ? is.filter(x => x.lid !== lid) : is);
  const updLine = (lid, k, v) => setItems(is => is.map(x => x.lid === lid ? { ...x, [k]: v } : x));

  // unit = prix TOTAL paye pour la ligne (pas unitaire). Choix UX du user.
  const lineTotal = (it) => Number(it.unit) || 0;
  const subItems = items.reduce((a, it) => a + lineTotal(it), 0);
  const totalCur = subItems + (Number(shipping) || 0) + (Number(platformFee) || 0);
  const totalEUR = toEUR(subItems) + toEUR(shipping) + toEUR(platformFee) + (Number(duty) || 0);

  const sourceLabel = source === "Autre" ? (sourceCustom.trim() || "Autre") : source;
  const validItems = items.filter(it => it.title.trim() && Number(it.unit) > 0 && Number(it.qty) > 0);
  const sourceOK = source !== "Autre" || sourceCustom.trim().length > 0;
  const valid = sourceOK && validItems.length > 0 && (currency === "EUR" || Number(rate) > 0);

  const submit = () => {
    if (!valid || submitting) return;
    setSubmitting(true);

    // repartition proportionnelle des frais (port + commission + douane) par ligne en EUR
    const shipEUR = toEUR(shipping);
    const feeEUR  = toEUR(platformFee);
    const dutyEUR = Number(duty) || 0;
    const overhead = shipEUR + feeEUR + dutyEUR;
    const itemsSubEUR = toEUR(subItems);

    const builtItems = validItems.map(it => {
      const lineSubEUR = toEUR(lineTotal(it));
      const share = itemsSubEUR > 0 ? lineSubEUR / itemsSubEUR : 0;
      const lineFinalEUR = lineSubEUR + overhead * share;
      return {
        title: it.title.trim(),
        cat: Number(it.cat),
        qty: Number(it.qty),
        jpy: currency === "JPY" ? Number(it.unit) : 0,
        eur: Number(lineFinalEUR.toFixed(2)),
      };
    });

    const shipment = currency === "JPY"
      ? {
          status: "ordered",
          method: sourceLabel,
          source: sourceLabel,
          currency: "JPY",
          weight: null,
          port_jpy: Number(shipping) || 0,
          rate: Number(rate) || 170,
          duty_eur: Number(duty) || 0,
          platform_fee_eur: toEUR(platformFee),
          date,
          notes,
        }
      : {
          status: "ordered",
          method: sourceLabel,
          source: sourceLabel,
          currency,
          weight: null,
          port_jpy: 0,
          port_eur: Number(toEUR(shipping).toFixed(2)),
          rate: currency === "EUR" ? 1 : Number(rate) || 1,
          duty_eur: Number(duty) || 0,
          platform_fee_eur: Number(toEUR(platformFee).toFixed(2)),
          date,
          notes,
        };

    // overlay reste visible pendant TOUT le backend (POST + resync).
    // ferme le drawer apres la promise pour eviter ecran vide pendant le wait.
    Promise.resolve(onCreate(shipment, builtItems))
      .then(() => onClose())
      .catch(() => setSubmitting(false));
  };

  return (
    <Drawer open onClose={submitting ? () => {} : onClose} wide
      title="Nouvelle commande"
      sub="Achat hors workflow Mercari (eBay, Vinted, Cardmarket, magasin…)"
      footer={
        <React.Fragment>
          <button className="btn" onClick={onClose} disabled={submitting}>Annuler</button>
          <div style={{ flex: 1 }}></div>
          <div className="mono" style={{ marginRight: 14, fontSize: 13 }}>
            <span style={{ color: "var(--ink-3)" }}>Total</span>
            <b style={{ marginLeft: 8, fontSize: 15 }}>{fmtEUR(totalEUR, { decimals: 2 })} €</b>
          </div>
          <button className="btn btn-primary" disabled={!valid || submitting} onClick={submit}>
            {submitting ? (
              <React.Fragment><span className="spinner" /> Creation…</React.Fragment>
            ) : (
              <React.Fragment><Icon name="check" /> Creer la commande</React.Fragment>
            )}
          </button>
        </React.Fragment>
      }
    >
      {submitting && (
        <div className="form-overlay">
          <div className="spinner-lg" />
          <div>Creation de la commande…</div>
        </div>
      )}

      {/* Section 1 : Source + date */}
      <div className="form-section">
        <div className="form-section-head">
          <div className="form-section-num">1</div>
          <div>
            <div className="form-section-title">Source de la commande</div>
            <div className="form-section-sub">D'ou vient cet achat ?</div>
          </div>
        </div>
        <div style={{ display: "grid", gridTemplateColumns: source === "Autre" ? "1fr 1fr 200px" : "1fr 200px", gap: 12 }}>
          <div className="field">
            <label>Plateforme</label>
            <select className="select" value={source} onChange={e => setSource(e.target.value)}>
              {ORDER_SOURCES.map(s => <option key={s.value} value={s.value}>{s.value}</option>)}
            </select>
          </div>
          {source === "Autre" && (
            <div className="field">
              <label>Preciser <span style={{ color: "var(--neg)" }}>*</span></label>
              <input className="input" placeholder="ex. Leboncoin, Pokemon Center US"
                     value={sourceCustom} onChange={e => setSourceCustom(e.target.value)} />
            </div>
          )}
          <div className="field">
            <label>Date d'achat</label>
            <input className="input mono" type="date" value={date} onChange={e => setDate(e.target.value)} />
          </div>
        </div>
      </div>

      {/* Section 2 : Devise */}
      <div className="form-section">
        <div className="form-section-head">
          <div className="form-section-num">2</div>
          <div>
            <div className="form-section-title">Devise</div>
            <div className="form-section-sub">{currency === "EUR" ? "Affiche en euros — pas de conversion necessaire" : `Conversion vers EUR au taux ${rate}`}</div>
          </div>
        </div>
        <div style={{ display: "flex", alignItems: "flex-end", gap: 14, flexWrap: "wrap" }}>
          <div className="cur-toggle">
            {CURRENCIES.map(c => (
              <button key={c.value} type="button"
                      className={currency === c.value ? "is-active" : ""}
                      onClick={() => setCurrency(c.value)}>
                <span className="cur-sym">{c.sym}</span>
                <span>{c.value}</span>
              </button>
            ))}
          </div>
          {currency !== "EUR" && (
            <div className="field" style={{ flex: "0 0 220px", marginBottom: 0 }}>
              <label>Taux : 1 EUR =</label>
              <div className="input-suffix-wrap">
                <input className="input mono" type="number" step="0.01" value={rate}
                       onChange={e => setRate(e.target.value)} />
                <span className="input-suffix">{sym}</span>
              </div>
            </div>
          )}
        </div>
      </div>

      {/* Section 3 : Articles */}
      <div className="form-section">
        <div className="form-section-head">
          <div className="form-section-num">3</div>
          <div>
            <div className="form-section-title">Articles</div>
            <div className="form-section-sub">{items.length} ligne{items.length > 1 ? "s" : ""} · {validItems.length} valide{validItems.length > 1 ? "s" : ""}</div>
          </div>
        </div>
        <div className="item-editor">
          <div className="item-editor-head">
            <div>Titre</div>
            <div>Categorie</div>
            <div className="right">Qte</div>
            <div className="right">Prix total paye</div>
            <div></div>
          </div>
          {items.map(it => (
            <div key={it.lid} className="item-editor-row">
              <input className="input" placeholder="ex. Display Surging Sparks"
                     value={it.title} onChange={e => updLine(it.lid, "title", e.target.value)} />
              <NewCatInline
                categories={categories}
                value={it.cat}
                onChange={id => updLine(it.lid, "cat", id)}
                actions={actions}
              />
              <input className="input mono right" type="number" min={1}
                     value={it.qty} onChange={e => updLine(it.lid, "qty", e.target.value)} />
              <div className="input-suffix-wrap">
                <input className="input mono right" type="number" step="0.01" placeholder="0.00"
                       value={it.unit} onChange={e => updLine(it.lid, "unit", e.target.value)} />
                <span className="input-suffix">{sym}</span>
              </div>
              <button className="btn btn-ghost btn-icon"
                      onClick={() => removeLine(it.lid)}
                      disabled={items.length <= 1}
                      aria-label="Supprimer la ligne">
                <Icon name="trash" />
              </button>
            </div>
          ))}
          <button className="btn btn-sm item-editor-add" onClick={addLine}>
            <Icon name="plus" /> Ajouter une ligne
          </button>
        </div>
      </div>

      {/* Section 4 : Frais additionnels */}
      <div className="form-section">
        <div className="form-section-head">
          <div className="form-section-num">4</div>
          <div>
            <div className="form-section-title">Frais additionnels</div>
            <div className="form-section-sub">Port, douane et commission plateforme</div>
          </div>
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 12 }}>
          <div className="field">
            <label>Frais de port</label>
            <div className="input-suffix-wrap">
              <input className="input mono" type="number" step="0.01" placeholder="0.00"
                     value={shipping} onChange={e => setShipping(e.target.value)} />
              <span className="input-suffix">{sym}</span>
            </div>
          </div>
          <div className="field">
            <label>Douane <span style={{ color: "var(--ink-3)", fontWeight: 400 }}>(si connue)</span></label>
            <div className="input-suffix-wrap">
              <input className="input mono" type="number" step="0.01" placeholder="0.00"
                     value={duty} onChange={e => setDuty(e.target.value)} />
              <span className="input-suffix">€</span>
            </div>
          </div>
          <div className="field">
            <label>Commission plateforme <span style={{ color: "var(--ink-3)", fontWeight: 400 }}>(opt.)</span></label>
            <div className="input-suffix-wrap">
              <input className="input mono" type="number" step="0.01" placeholder="0.00"
                     value={platformFee} onChange={e => setPlatformFee(e.target.value)} />
              <span className="input-suffix">{sym}</span>
            </div>
          </div>
        </div>
        <div className="field" style={{ marginTop: 10 }}>
          <label>Note <span style={{ color: "var(--ink-3)", fontWeight: 400 }}>(opt.)</span></label>
          <input className="input" placeholder="ex. promo -10%, code XYZ, vendeur reactif…"
                 value={notes} onChange={e => setNotes(e.target.value)} />
        </div>
      </div>

      {/* Recap total */}
      <div className="recap-card">
        <div className="recap-row">
          <span>Sous-total articles</span>
          <span className="mono">{sym}{subItems.toFixed(2)} <span className="recap-eur">({fmtEUR(toEUR(subItems))} €)</span></span>
        </div>
        <div className="recap-row">
          <span>Frais de port</span>
          <span className="mono">{sym}{(Number(shipping) || 0).toFixed(2)} <span className="recap-eur">({fmtEUR(toEUR(shipping))} €)</span></span>
        </div>
        {Number(platformFee) > 0 && (
          <div className="recap-row">
            <span>Commission plateforme</span>
            <span className="mono">{sym}{(Number(platformFee) || 0).toFixed(2)} <span className="recap-eur">({fmtEUR(toEUR(platformFee))} €)</span></span>
          </div>
        )}
        {Number(duty) > 0 && (
          <div className="recap-row">
            <span>Douane</span>
            <span className="mono">{fmtEUR(Number(duty))} €</span>
          </div>
        )}
        <div className="recap-row recap-row-total">
          <span>Total final</span>
          <span className="mono">
            {currency !== "EUR" && <span className="recap-cur">{sym}{totalCur.toFixed(2)}</span>}
            <b>{fmtEUR(totalEUR, { decimals: 2 })} €</b>
          </span>
        </div>
      </div>
    </Drawer>
  );
};

window.ShipmentsScreen = ShipmentsScreen;
