/* ══════════════════════════════════════════════════════════════════════════
   app.jsx — Application MSCV (React 18 via CDN, compile Babel navigateur).
   Pilotage de la rentabilite au dossier d'une etude d'huissiers.
   Bespoke Operations.
   ════════════════════════════════════════════════════════════════════════ */
const { useState, useEffect, useMemo, useRef, useCallback } = React;
const RC = window.Recharts || {};
const {
  ResponsiveContainer, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend,
  ScatterChart, Scatter, ZAxis, Cell, PieChart, Pie
} = RC;

const M = window.MSCV;
const Store = window.MSCVStore;

/* ══ Formatage ══ */
const _eur0 = new Intl.NumberFormat('fr-BE', { style: 'currency', currency: 'EUR', maximumFractionDigits: 0 });
const _eur2 = new Intl.NumberFormat('fr-BE', { style: 'currency', currency: 'EUR', minimumFractionDigits: 0, maximumFractionDigits: 2 });
const _num0 = new Intl.NumberFormat('fr-BE', { maximumFractionDigits: 0 });
function eur0(n) { return _eur0.format(Math.round(n || 0)); }
function eur(n) { return _eur2.format(n || 0); }
function eurK(n) { const v = n || 0; return Math.abs(v) >= 10000 ? (v / 1000).toFixed(0) + 'k€' : Math.round(v) + '€'; }
function pct(n) { return (n || 0).toFixed(1) + ' %'; }
function cx() { return Array.prototype.filter.call(arguments, Boolean).join(' '); }

/* ══ Store hook ══ */
function useStore() {
  return React.useSyncExternalStore(Store.subscribe, Store.getState);
}

/* ══ Toasts ══ */
const toastBus = (() => {
  let items = []; const subs = new Set();
  return {
    push(msg, type) { const id = Math.random(); items = items.concat([{ id, msg, type }]); subs.forEach(f => f()); setTimeout(() => { items = items.filter(t => t.id !== id); subs.forEach(f => f()); }, 2600); },
    subscribe(f) { subs.add(f); return () => subs.delete(f); },
    get() { return items; }
  };
})();
function showToast(msg, type) { toastBus.push(msg, type || 'ok'); }
function Toasts() {
  const items = React.useSyncExternalStore(toastBus.subscribe, toastBus.get);
  return <div className="toast-wrap">{items.map(t => <div key={t.id} className={cx('toast', t.type)}>{t.msg}</div>)}</div>;
}

/* ══ Icones (inline SVG, feather-style) ══ */
const PATHS = {
  grid: 'M3 3h7v7H3zM14 3h7v7h-7zM14 14h7v7h-7zM3 14h7v7H3z',
  folder: 'M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z',
  plus: 'M12 5v14M5 12h14',
  calc: 'M5 3h14v18H5zM9 7h6M8 11h.01M12 11h.01M16 11h.01M8 15h.01M12 15h.01M16 15h4',
  trash: 'M3 6h18M8 6V4h8v2M19 6l-1 14H6L5 6',
  edit: 'M11 4H4v16h16v-7M18.5 2.5a2.1 2.1 0 0 1 3 3L12 15l-4 1 1-4z',
  back: 'M19 12H5M12 19l-7-7 7-7',
  search: 'M11 19a8 8 0 1 1 0-16 8 8 0 0 1 0 16zM21 21l-4.3-4.3',
  download: 'M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3',
  x: 'M18 6L6 18M6 6l12 12',
  check: 'M20 6L9 17l-5-5',
  chev: 'M6 9l6 6 6-6',
  menu: 'M3 12h18M3 6h18M3 18h18',
  copy: 'M9 9h11v11H9zM5 15H4V4h11v1',
  refresh: 'M21 2v6h-6M3 12a9 9 0 0 1 15-6.7L21 8M3 22v-6h6M21 12a9 9 0 0 1-15 6.7L3 16'
};
function Icon({ name, size = 17, fill = false, style }) {
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill={fill ? 'currentColor' : 'none'}
      stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={style}>
      <path d={PATHS[name]} />
    </svg>
  );
}

/* ══ Primitives UI ══ */
function SegmentBadge({ id, court }) {
  const s = M.segmentById(id);
  if (!s) return null;
  return <span className="badge" style={{ color: s.color, background: s.color + '1f' }}><span className="dot" />{court ? s.court : s.label}</span>;
}
function MscvPill({ taux, children }) {
  const lvl = M.mscvNiveau(taux);
  return <span className={cx('mscv-pill', 'lvl-' + lvl)}>{children != null ? children : pct(taux)}</span>;
}
function StatutBadge({ statut, muted }) {
  const st = M.statutById(statut);
  if (!st) return muted ? <span className="sec" style={{ fontSize: 12 }}>—</span> : null;
  return <span className="badge" style={{ color: st.color, background: st.color + '1f' }}><span className="dot" />{st.label}</span>;
}
function Field({ label, hint, children }) {
  return <div className="field"><label>{label}</label>{children}{hint && <span className="hint">{hint}</span>}</div>;
}
function Toggle({ checked, onChange, label }) {
  return (
    <label className="toggle">
      <input type="checkbox" checked={!!checked} onChange={e => onChange(e.target.checked)} />
      <span className="toggle-track" />
      {label && <span>{label}</span>}
    </label>
  );
}

/* Champ numerique : etat string interne, parse a la remontee. */
function NumField({ value, onChange, placeholder, suffix, step }) {
  return (
    <div style={{ position: 'relative' }}>
      <input className="mono" type="number" inputMode="decimal" step={step || 'any'}
        value={value} placeholder={placeholder || '0'}
        onChange={e => onChange(e.target.value)}
        style={suffix ? { paddingRight: 38 } : null} />
      {suffix && <span style={{ position: 'absolute', right: 11, top: '50%', transform: 'translateY(-50%)', fontSize: 12, color: 'var(--color-text-muted)', pointerEvents: 'none' }}>{suffix}</span>}
    </div>
  );
}

/* Multi-select avec recherche + checkboxes */
function MultiSelect({ label, options, selected, onChange, searchable, labelFor }) {
  const [open, setOpen] = useState(false);
  const [q, setQ] = useState('');
  const ref = useRef(null);
  const lbl = o => labelFor ? labelFor(o) : String(o);
  useEffect(() => {
    if (!open) return;
    const h = e => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', h);
    return () => document.removeEventListener('mousedown', h);
  }, [open]);
  const toggle = v => onChange(selected.includes(v) ? selected.filter(x => x !== v) : selected.concat([v]));
  const filtered = q ? options.filter(o => lbl(o).toLowerCase().includes(q.toLowerCase())) : options;
  return (
    <div className="field ms" ref={ref}>
      <label>{label}</label>
      <button type="button" className="btn ms-btn" onClick={() => setOpen(o => !o)}>
        <span className="sec" style={{ fontWeight: 500 }}>{selected.length ? selected.length + ' selectionne' + (selected.length > 1 ? 's' : '') : 'Tous'}</span>
        <Icon name="chev" size={14} />
      </button>
      {open && (
        <div className="ms-pop">
          {searchable && <input className="ms-search" type="text" placeholder="Rechercher…" value={q} onChange={e => setQ(e.target.value)} autoFocus />}
          {selected.length > 0 && <div className="ms-opt" style={{ color: 'var(--color-accent)' }} onClick={() => onChange([])}><Icon name="x" size={14} /> Tout effacer</div>}
          {filtered.map(o => (
            <div key={o} className={cx('ms-opt', selected.includes(o) && 'on')} onClick={() => toggle(o)}>
              <span className="ms-check">{selected.includes(o) && <Icon name="check" size={11} />}</span>
              <span>{lbl(o)}</span>
            </div>
          ))}
          {filtered.length === 0 && <div className="ms-opt muted">Aucun resultat</div>}
        </div>
      )}
    </div>
  );
}

/* ══ Routeur (hash) ══ */
function parseHash() {
  const h = (location.hash || '#/dashboard').replace(/^#\/?/, '');
  const parts = h.split('/');
  if (parts[0] === 'dossier' && parts[1]) return { name: 'detail', id: parts[1] };
  if (parts[0] === 'edit' && parts[1]) return { name: 'form', id: parts[1] };
  if (['dashboard', 'dossiers', 'nouveau', 'tarif'].includes(parts[0])) return { name: parts[0] };
  return { name: 'dashboard' };
}
function navigate(hash) { location.hash = hash; }
function useRoute() {
  const [route, setRoute] = useState(parseHash());
  useEffect(() => {
    const h = () => { setRoute(parseHash()); window.scrollTo(0, 0); };
    window.addEventListener('hashchange', h);
    return () => window.removeEventListener('hashchange', h);
  }, []);
  return route;
}

/* ══ Filtres (partages dashboard + liste) ══ */
const EMPTY_FILTERS = { segment: [], secteur: [], donneur: [], tags: [], statut: [], from: '', to: '' };
function applyFilters(dossiers, f) {
  return dossiers.filter(d => {
    if (f.segment.length && !f.segment.includes(d.segment)) return false;
    if (f.secteur.length && !f.secteur.includes(d.secteur)) return false;
    if (f.donneur.length && !f.donneur.includes(d.donneurOrdre)) return false;
    if (f.tags.length && !f.tags.some(t => (d.tags || []).includes(t))) return false;
    if (f.statut && f.statut.length && !f.statut.includes(d.statutRecouvrement)) return false;
    if (f.from && d.dateCreation < f.from) return false;
    if (f.to && d.dateCreation > f.to) return false;
    return true;
  });
}
function FiltersBar({ dossiers, filters, setFilters }) {
  const donneurs = useMemo(() => Array.from(new Set(dossiers.map(d => d.donneurOrdre))).sort(), [dossiers]);
  const allTags = useMemo(() => Array.from(new Set(dossiers.flatMap(d => d.tags || []))).sort(), [dossiers]);
  const active = filters.segment.length + filters.secteur.length + filters.donneur.length + filters.tags.length + (filters.statut ? filters.statut.length : 0) + (filters.from ? 1 : 0) + (filters.to ? 1 : 0);
  return (
    <div className="card" style={{ marginBottom: 16 }}>
      <div className="filters">
        <MultiSelect label="Segment" options={M.SEGMENTS.map(s => s.id)} selected={filters.segment}
          onChange={v => setFilters({ ...filters, segment: v })} labelFor={id => { const s = M.segmentById(id); return s ? s.court : id; }} />
        <MultiSelect label="Statut recouvr." options={M.STATUTS_RECOUVREMENT.map(s => s.id)} selected={filters.statut || []}
          onChange={v => setFilters({ ...filters, statut: v })} labelFor={id => { const s = M.statutById(id); return s ? s.label : id; }} />
        <MultiSelect label="Secteur" options={M.SECTEURS} selected={filters.secteur}
          onChange={v => setFilters({ ...filters, secteur: v })} searchable />
        <MultiSelect label="Donneur d'ordre" options={donneurs} selected={filters.donneur}
          onChange={v => setFilters({ ...filters, donneur: v })} searchable />
        <MultiSelect label="Tags" options={allTags} selected={filters.tags}
          onChange={v => setFilters({ ...filters, tags: v })} searchable />
        <Field label="Du"><input type="date" value={filters.from} onChange={e => setFilters({ ...filters, from: e.target.value })} /></Field>
        <Field label="Au"><input type="date" value={filters.to} onChange={e => setFilters({ ...filters, to: e.target.value })} /></Field>
        {active > 0 && <button className="btn btn-ghost" onClick={() => setFilters({ ...EMPTY_FILTERS })}><Icon name="x" size={14} /> Reset ({active})</button>}
      </div>
    </div>
  );
}

/* ══ Tooltips graphiques (theme sombre) ══ */
function ChartTip({ active, payload, label, money }) {
  if (!active || !payload || !payload.length) return null;
  return (
    <div style={{ background: 'var(--color-surface)', border: '1px solid var(--color-border-light)', borderRadius: 8, padding: '9px 12px', fontSize: 12, boxShadow: '0 10px 30px rgba(0,0,0,.5)' }}>
      {label != null && <div style={{ fontWeight: 600, marginBottom: 5 }}>{label}</div>}
      {payload.map((p, i) => (
        <div key={i} style={{ display: 'flex', justifyContent: 'space-between', gap: 16 }}>
          <span style={{ color: p.color || p.fill }}>{p.name}</span>
          <span className="mono" style={{ color: 'var(--color-text-primary)' }}>{money ? eur0(p.value) : p.value}</span>
        </div>
      ))}
    </div>
  );
}
const AXIS = { fill: '#A0A0B0', fontSize: 11 };
const GRID = '#1E2A45';

/* ══ Tri de tableau ══ */
function useSort(initialKey, initialDir) {
  const [sort, setSort] = useState({ key: initialKey, dir: initialDir || 'desc' });
  const onSort = key => setSort(s => s.key === key ? { key, dir: s.dir === 'asc' ? 'desc' : 'asc' } : { key, dir: 'desc' });
  function apply(rows, accessors) {
    const acc = accessors[sort.key];
    if (!acc) return rows;
    const sorted = rows.slice().sort((a, b) => {
      const va = acc(a), vb = acc(b);
      if (typeof va === 'string') return va.localeCompare(vb);
      return va - vb;
    });
    return sort.dir === 'asc' ? sorted : sorted.reverse();
  }
  return { sort, onSort, apply };
}
function SortTh({ label, k, sort, onSort, num }) {
  return (
    <th className={cx('sortable', num && 'num')} onClick={() => onSort(k)}>
      {label}{sort.key === k && <span className="arrow">{sort.dir === 'asc' ? '↑' : '↓'}</span>}
    </th>
  );
}

/* ══════════════════════ DASHBOARD ══════════════════════ */
function ChargesInput() {
  const state = useStore();
  const [v, setV] = useState(String(state.chargesFixesMensuel));
  useEffect(() => { setV(String(state.chargesFixesMensuel)); }, [state.chargesFixesMensuel]);
  return (
    <input className="mono" type="number" value={v} style={{ maxWidth: 150 }}
      onChange={e => setV(e.target.value)}
      onBlur={() => Store.setChargesFixesMensuel(M.num(v))} />
  );
}

function Dashboard({ filters, setFilters }) {
  const state = useStore();
  const filtered = useMemo(() => applyFilters(state.dossiers, filters), [state.dossiers, filters]);
  const rows = useMemo(() => filtered.map(d => Object.assign({ d: d }, M.getMSCV(d))), [filtered]);

  const caTotal = rows.reduce((s, r) => s + r.produit, 0);
  const coutTotal = rows.reduce((s, r) => s + r.cout, 0);
  const mscvTotal = rows.reduce((s, r) => s + r.mscv, 0);
  const tauxGlobal = caTotal > 0 ? mscvTotal / caTotal * 100 : 0;
  const resultat = mscvTotal - state.chargesFixesMensuel;

  const bySeg = useMemo(() => M.SEGMENTS.map(s => {
    const rs = rows.filter(r => r.d.segment === s.id);
    return {
      id: s.id, name: s.court, color: s.color, count: rs.length,
      CA: Math.round(rs.reduce((a, r) => a + r.produit, 0)),
      Couts: Math.round(rs.reduce((a, r) => a + r.cout, 0)),
      MSCV: Math.round(rs.reduce((a, r) => a + r.mscv, 0))
    };
  }).filter(x => x.count > 0), [rows]);

  const byDonneur = useMemo(() => {
    const map = {};
    rows.forEach(r => {
      const k = r.d.donneurOrdre || '—';
      if (!map[k]) map[k] = { name: k, count: 0, ca: 0, cout: 0, mscv: 0, recouvr: 0, nCre: 0 };
      map[k].count++; map[k].ca += r.produit; map[k].cout += r.cout; map[k].mscv += r.mscv;
      if (M.aCreance(r.d)) { map[k].recouvr += M.tauxRecouvreReel(r.d); map[k].nCre++; }
    });
    return Object.keys(map).map(k => {
      const o = map[k];
      return Object.assign(o, { taux: o.ca > 0 ? o.mscv / o.ca * 100 : 0, recouvrMoy: o.nCre ? o.recouvr / o.nCre : 0 });
    });
  }, [rows]);

  const suivi = useMemo(() => {
    const counts = { en_cours: 0, partiel: 0, solde: 0, irrecouvrable: 0 };
    let encaisse = 0, du = 0, nCre = 0, sans = 0;
    rows.forEach(r => {
      if (!M.aCreance(r.d)) { sans++; return; }
      nCre++;
      const st = r.d.statutRecouvrement || 'en_cours';
      if (counts[st] !== undefined) counts[st]++;
      encaisse += M.getEncaisse(r.d);
      du += M.num(r.d.montantTotalARecouvrer);
    });
    return { counts, encaisse, du, nCre, sans, tauxReel: du > 0 ? encaisse / du * 100 : 0 };
  }, [rows]);

  const donut = bySeg.map(s => ({ name: s.name, value: Math.max(0, s.MSCV), real: s.MSCV, color: s.color })).filter(x => x.value > 0);

  const sorter = useSort('mscv', 'desc');
  const acc = {
    name: o => o.name, count: o => o.count, ca: o => o.ca,
    mscv: o => o.mscv, taux: o => o.taux, recouvrMoy: o => o.recouvrMoy
  };
  const ranked = sorter.apply(byDonneur, acc);

  return (
    <div>
      <FiltersBar dossiers={state.dossiers} filters={filters} setFilters={setFilters} />

      <div className="grid kpis" style={{ marginBottom: 16 }}>
        <div className="kpi" style={{ '--accent': '#60a5fa' }}>
          <div className="kpi-label">CA total HT</div>
          <div className="kpi-value">{eur0(caTotal)}</div>
          <div className="kpi-sub">{rows.length} dossier{rows.length > 1 ? 's' : ''} · couts var. {eur0(coutTotal)}</div>
        </div>
        <div className="kpi" style={{ '--accent': M.mscvNiveau(tauxGlobal) === 'success' ? '#4ade80' : M.mscvNiveau(tauxGlobal) === 'warning' ? '#f59e0b' : '#f87171' }}>
          <div className="kpi-label">MSCV totale</div>
          <div className="kpi-value">{eur0(mscvTotal)}</div>
          <div className="kpi-sub">Taux MSCV global <MscvPill taux={tauxGlobal} /></div>
        </div>
        <div className="kpi" style={{ '--accent': resultat >= 0 ? '#4ade80' : '#f87171' }}>
          <div className="kpi-label">Resultat d'exploitation</div>
          <div className="kpi-value" style={{ color: resultat >= 0 ? 'var(--color-success)' : 'var(--color-danger)' }}>{eur0(resultat)}</div>
          <div className="kpi-sub" style={{ display: 'flex', alignItems: 'center', gap: 7 }}>MSCV − charges fixes <ChargesInput /></div>
        </div>
        <div className="kpi" style={{ '--accent': '#E94560' }}>
          <div className="kpi-label">Dossiers actifs</div>
          <div className="kpi-value">{rows.length}</div>
          <div className="kpi-sub">{byDonneur.length} donneur{byDonneur.length > 1 ? 's' : ''} d'ordre · {bySeg.length} segment{bySeg.length > 1 ? 's' : ''}</div>
        </div>
      </div>

      <div className="card" style={{ marginBottom: 16 }}>
        <div className="card-title">Suivi du recouvrement <span className="muted" style={{ fontWeight: 400, fontFamily: 'var(--font-mono)' }}>{eur0(suivi.encaisse)} encaisses / {eur0(suivi.du)} dus · {suivi.tauxReel.toFixed(0)} % reel{suivi.sans ? ' · ' + suivi.sans + ' sans creance' : ''}</span></div>
        <div className="grid" style={{ gridTemplateColumns: 'repeat(4,1fr)' }}>
          {M.STATUTS_RECOUVREMENT.map(st => (
            <div key={st.id} style={{ border: '1px solid var(--color-border)', borderRadius: 'var(--radius-md)', padding: '12px 14px', background: st.color + '0f' }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
                <span style={{ width: 8, height: 8, borderRadius: '50%', background: st.color, display: 'inline-block' }} />
                <span className="sec" style={{ fontSize: 12 }}>{st.label}</span>
              </div>
              <div className="mono" style={{ fontSize: 22, fontWeight: 600, marginTop: 6, color: st.color }}>{suivi.counts[st.id]}</div>
              <div className="muted" style={{ fontSize: 11 }}>dossier{suivi.counts[st.id] > 1 ? 's' : ''}</div>
            </div>
          ))}
        </div>
      </div>

      <div className="grid" style={{ gridTemplateColumns: '1.55fr 1fr', marginBottom: 16 }}>
        <div className="card">
          <div className="card-title">CA · Couts variables · MSCV par segment</div>
          <div style={{ height: 290 }}>
            <ResponsiveContainer width="100%" height="100%">
              <BarChart data={bySeg} margin={{ top: 6, right: 8, left: 0, bottom: 0 }}>
                <CartesianGrid strokeDasharray="3 3" stroke={GRID} vertical={false} />
                <XAxis dataKey="name" tick={AXIS} axisLine={{ stroke: GRID }} tickLine={false} />
                <YAxis tick={AXIS} axisLine={false} tickLine={false} tickFormatter={eurK} width={46} />
                <Tooltip content={<ChartTip money />} cursor={{ fill: 'rgba(255,255,255,0.03)' }} />
                <Legend wrapperStyle={{ fontSize: 12 }} />
                <Bar dataKey="CA" name="CA HT" fill="#60a5fa" radius={[3, 3, 0, 0]} />
                <Bar dataKey="Couts" name="Couts var." fill="#533483" radius={[3, 3, 0, 0]} />
                <Bar dataKey="MSCV" name="MSCV" fill="#E94560" radius={[3, 3, 0, 0]} />
              </BarChart>
            </ResponsiveContainer>
          </div>
        </div>

        <div className="card">
          <div className="card-title">Repartition MSCV par segment</div>
          {donut.length ? (
            <div style={{ height: 290 }}>
              <ResponsiveContainer width="100%" height="100%">
                <PieChart>
                  <Pie data={donut} dataKey="value" nameKey="name" innerRadius={62} outerRadius={96} paddingAngle={2} stroke="none">
                    {donut.map((e, i) => <Cell key={i} fill={e.color} />)}
                  </Pie>
                  <Tooltip content={<ChartTip money />} />
                  <Legend wrapperStyle={{ fontSize: 12 }} />
                </PieChart>
              </ResponsiveContainer>
            </div>
          ) : <div className="empty">Aucune MSCV positive a afficher</div>}
        </div>
      </div>

      <div className="card" style={{ marginBottom: 16 }}>
        <div className="card-title">Rentabilite par donneur d'ordre — taux MSCV vs volume</div>
        <div style={{ height: 300 }}>
          <ResponsiveContainer width="100%" height="100%">
            <ScatterChart margin={{ top: 10, right: 16, left: 0, bottom: 10 }}>
              <CartesianGrid strokeDasharray="3 3" stroke={GRID} />
              <XAxis type="number" dataKey="count" name="Dossiers" tick={AXIS} axisLine={{ stroke: GRID }} tickLine={false}
                label={{ value: 'Volume (nb dossiers)', position: 'insideBottom', offset: -4, fill: '#606070', fontSize: 11 }} />
              <YAxis type="number" dataKey="taux" name="Taux MSCV" unit="%" tick={AXIS} axisLine={false} tickLine={false} width={42} />
              <ZAxis type="number" dataKey="ca" range={[80, 620]} name="CA" />
              <Tooltip cursor={{ strokeDasharray: '3 3', stroke: GRID }} content={({ active, payload }) => {
                if (!active || !payload || !payload.length) return null;
                const p = payload[0].payload;
                return <div style={{ background: 'var(--color-surface)', border: '1px solid var(--color-border-light)', borderRadius: 8, padding: '9px 12px', fontSize: 12, boxShadow: '0 10px 30px rgba(0,0,0,.5)' }}>
                  <div style={{ fontWeight: 600, marginBottom: 4 }}>{p.name}</div>
                  <div className="sec">{p.count} dossier(s) · CA {eur0(p.ca)}</div>
                  <div style={{ marginTop: 3 }}>Taux MSCV <MscvPill taux={p.taux} /></div>
                </div>;
              }} />
              <Scatter data={byDonneur}>
                {byDonneur.map((o, i) => <Cell key={i} fill={M.mscvNiveau(o.taux) === 'success' ? '#4ade80' : M.mscvNiveau(o.taux) === 'warning' ? '#f59e0b' : '#f87171'} fillOpacity={0.78} />)}
              </Scatter>
            </ScatterChart>
          </ResponsiveContainer>
        </div>
        <div className="muted" style={{ fontSize: 11, marginTop: 6 }}>Taille de bulle ∝ CA HT du donneur d'ordre. Couleur = niveau de taux MSCV (vert &gt; 40 %, orange 20–40 %, rouge &lt; 20 %).</div>
      </div>

      <div className="card">
        <div className="card-title">Classement donneurs d'ordre</div>
        <div className="table-wrap">
          <table>
            <thead>
              <tr>
                <SortTh label="Donneur d'ordre" k="name" sort={sorter.sort} onSort={sorter.onSort} />
                <SortTh label="Dossiers" k="count" sort={sorter.sort} onSort={sorter.onSort} num />
                <SortTh label="CA HT" k="ca" sort={sorter.sort} onSort={sorter.onSort} num />
                <SortTh label="MSCV" k="mscv" sort={sorter.sort} onSort={sorter.onSort} num />
                <SortTh label="Taux MSCV" k="taux" sort={sorter.sort} onSort={sorter.onSort} num />
                <SortTh label="Recouvr. moyen" k="recouvrMoy" sort={sorter.sort} onSort={sorter.onSort} num />
              </tr>
            </thead>
            <tbody>
              {ranked.map(o => (
                <tr key={o.name}>
                  <td>{o.name}</td>
                  <td className="num">{o.count}</td>
                  <td className="num">{eur0(o.ca)}</td>
                  <td className="num">{eur0(o.mscv)}</td>
                  <td className="num"><MscvPill taux={o.taux} /></td>
                  <td className="num">{o.recouvrMoy.toFixed(0)} %</td>
                </tr>
              ))}
              {!ranked.length && <tr><td colSpan="6" className="empty">Aucun dossier ne correspond aux filtres</td></tr>}
            </tbody>
          </table>
        </div>
      </div>

      <LegalFooter />
    </div>
  );
}

/* ══════════════════════ LISTE DOSSIERS ══════════════════════ */
function DossiersList({ filters, setFilters }) {
  const state = useStore();
  const filtered = useMemo(() => applyFilters(state.dossiers, filters), [state.dossiers, filters]);
  const rows = useMemo(() => filtered.map(d => Object.assign({ d: d }, M.getMSCV(d))), [filtered]);
  const sorter = useSort('date', 'desc');
  const acc = {
    reference: r => r.d.reference, donneur: r => r.d.donneurOrdre, segment: r => r.d.segment,
    secteur: r => r.d.secteur, creance: r => M.num(r.d.montantCreance), mscv: r => r.mscv,
    taux: r => r.taux, date: r => r.d.dateCreation,
    statut: r => { const st = M.statutById(r.d.statutRecouvrement); return st ? st.label : 'zzz'; }
  };
  const sorted = sorter.apply(rows, acc);
  return (
    <div>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16, gap: 12 }}>
        <div className="sec" style={{ fontSize: 13 }}>{rows.length} dossier{rows.length > 1 ? 's' : ''} affiche{rows.length > 1 ? 's' : ''}</div>
        <button className="btn btn-accent" onClick={() => navigate('#/nouveau')}><Icon name="plus" size={15} /> Nouveau dossier</button>
      </div>
      <FiltersBar dossiers={state.dossiers} filters={filters} setFilters={setFilters} />
      <div className="card" style={{ padding: 0, overflow: 'hidden' }}>
        <div className="table-wrap" style={{ border: 'none' }}>
          <table>
            <thead>
              <tr>
                <SortTh label="Reference" k="reference" sort={sorter.sort} onSort={sorter.onSort} />
                <SortTh label="Donneur d'ordre" k="donneur" sort={sorter.sort} onSort={sorter.onSort} />
                <SortTh label="Segment" k="segment" sort={sorter.sort} onSort={sorter.onSort} />
                <SortTh label="Secteur" k="secteur" sort={sorter.sort} onSort={sorter.onSort} />
                <th>Tags</th>
                <SortTh label="Creance" k="creance" sort={sorter.sort} onSort={sorter.onSort} num />
                <SortTh label="MSCV" k="mscv" sort={sorter.sort} onSort={sorter.onSort} num />
                <SortTh label="Taux" k="taux" sort={sorter.sort} onSort={sorter.onSort} num />
                <SortTh label="Statut" k="statut" sort={sorter.sort} onSort={sorter.onSort} />
                <SortTh label="Date" k="date" sort={sorter.sort} onSort={sorter.onSort} />
              </tr>
            </thead>
            <tbody>
              {sorted.map(r => (
                <tr key={r.d.id} className="clickable" onClick={() => navigate('#/dossier/' + r.d.id)}>
                  <td className="mono" style={{ fontSize: 12 }}>{r.d.reference}</td>
                  <td>{r.d.donneurOrdre}</td>
                  <td><SegmentBadge id={r.d.segment} court /></td>
                  <td className="sec">{r.d.secteur}</td>
                  <td>
                    <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap', maxWidth: 180 }}>
                      {(r.d.tags || []).slice(0, 3).map(t => <span key={t} className="tag">{t}</span>)}
                      {(r.d.tags || []).length > 3 && <span className="tag">+{r.d.tags.length - 3}</span>}
                    </div>
                  </td>
                  <td className="num">{M.num(r.d.montantCreance) ? eur0(r.d.montantCreance) : '—'}</td>
                  <td className="num">{eur0(r.mscv)}</td>
                  <td className="num"><MscvPill taux={r.taux} /></td>
                  <td>{M.aCreance(r.d) ? <StatutBadge statut={r.d.statutRecouvrement} /> : <span className="sec">—</span>}</td>
                  <td className="sec mono" style={{ fontSize: 12 }}>{r.d.dateCreation}</td>
                </tr>
              ))}
              {!sorted.length && <tr><td colSpan="10" className="empty">Aucun dossier — ajustez les filtres ou creez-en un</td></tr>}
            </tbody>
          </table>
        </div>
      </div>
      <LegalFooter />
    </div>
  );
}

/* ══════════════════════ RECAP (panel calcul live) ══════════════════════ */
function RecapPanel({ obj, sticky }) {
  const prod = M.getProduitLignes(obj);
  const couts = M.getCoutLignes(obj);
  const r = M.getMSCV(obj);
  const lvl = M.mscvNiveau(r.taux);
  return (
    <div className={cx('card', sticky && 'recap')}>
      <div className="card-title">Decompte tarifaire <span className="badge" style={{ color: 'var(--color-text-muted)', background: 'rgba(255,255,255,0.04)' }}>{prod.mode === 'libre' ? 'Tarif libre' : 'AR 2024'}</span></div>
      {prod.lignes.length ? prod.lignes.map((l, i) => (
        <div className="recap-line" key={i}>
          <span className="lbl">{l.label}<small>{l.detail}</small></span>
          <span className="val">{eur(l.montant)}</span>
        </div>
      )) : <div className="muted" style={{ fontSize: 12, padding: '6px 0' }}>Aucun poste de produit saisi</div>}
      <div className="recap-total"><span>Produit HT</span><span className="val" style={{ color: '#60a5fa' }}>{eur(r.produit)}</span></div>

      <div className="card-title" style={{ marginTop: 18 }}>Couts variables</div>
      {couts.lignes.filter(l => l.montant).map((l, i) => (
        <div className="recap-line" key={i}><span className="lbl">{l.label}<small>{l.detail}</small></span><span className="val">{eur(l.montant)}</span></div>
      ))}
      <div className="recap-total"><span>Total couts variables</span><span className="val" style={{ color: '#a78bfa' }}>{eur(r.cout)}</span></div>

      <div style={{ marginTop: 16, padding: 14, borderRadius: 'var(--radius-md)', background: lvl === 'success' ? 'rgba(74,222,128,0.1)' : lvl === 'warning' ? 'rgba(245,158,11,0.1)' : 'rgba(248,113,113,0.1)', border: '1px solid ' + (lvl === 'success' ? 'rgba(74,222,128,0.3)' : lvl === 'warning' ? 'rgba(245,158,11,0.3)' : 'rgba(248,113,113,0.3)') }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
          <span style={{ fontWeight: 700, fontSize: 15 }}>MSCV</span>
          <span className="mono" style={{ fontWeight: 700, fontSize: 19, color: 'var(--color-' + lvl + ')' }}>{eur(r.mscv)}</span>
        </div>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 6 }}>
          <span className="sec" style={{ fontSize: 12 }}>Taux MSCV</span>
          <MscvPill taux={r.taux} />
        </div>
      </div>
    </div>
  );
}

/* ══════════════════════ EDITEUR DE TAGS ══════════════════════ */
function TagEditor({ tags, onChange }) {
  const [input, setInput] = useState('');
  const add = t => { t = t.trim(); if (t && !tags.includes(t)) onChange(tags.concat([t])); setInput(''); };
  return (
    <div>
      <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginBottom: tags.length ? 9 : 0 }}>
        {tags.map(t => <span key={t} className="tag removable">{t}<span className="x" onClick={() => onChange(tags.filter(x => x !== t))}>×</span></span>)}
      </div>
      <input type="text" placeholder="Ajouter un tag puis Entree…" value={input}
        onChange={e => setInput(e.target.value)}
        onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); add(input); } }} />
      <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginTop: 9 }}>
        {M.TAGS_SUGGERES.filter(t => !tags.includes(t)).slice(0, 12).map(t =>
          <span key={t} className="tag chip-pick" onClick={() => add(t)}>+ {t}</span>
        )}
      </div>
    </div>
  );
}

/* ══════════════════════ FORMULAIRE DOSSIER ══════════════════════ */
function emptyForm() {
  return {
    reference: 'L&P-2026-' + String(Math.floor(1000 + Math.random() * 9000)),
    segment: 'judiciaire', secteur: 'Energie', donneurOrdre: '', tags: [], notes: '',
    montantCreance: '', montantTotalARecouvrer: '', estCreanceEssentielle: false,
    statutRecouvrement: 'en_cours', montantRecouvreReel: '',
    nbActesSignifies: '', nbAdressesSupplementaires: '', nbUnites30min: '', nbDeplacements: '', nbPagesDeclConforme: '', nbRecherches: '', nbSommations: '',
    inclureForfaitAdmin: false, inclureHonRecouvrement: false,
    tarifMode: 'ar', tarifLibreHT: '',
    tempsHeures: '', tauxHoraireCharge: '', fraisEnvoi: '', fraisTechSI: '', autresCoutsVariables: ''
  };
}
function formFromDossier(d) {
  const s = String;
  return {
    reference: d.reference, segment: d.segment, secteur: d.secteur, donneurOrdre: d.donneurOrdre,
    tags: (d.tags || []).slice(), notes: d.notes || '',
    montantCreance: s(d.montantCreance), montantTotalARecouvrer: s(d.montantTotalARecouvrer),
    estCreanceEssentielle: !!d.estCreanceEssentielle,
    statutRecouvrement: d.statutRecouvrement || 'en_cours',
    montantRecouvreReel: (d.montantRecouvreReel === 0 || d.montantRecouvreReel) ? s(d.montantRecouvreReel) : '',
    nbActesSignifies: s(d.nbActesSignifies), nbAdressesSupplementaires: s(d.nbAdressesSupplementaires),
    nbUnites30min: s(d.nbUnites30min), nbDeplacements: s(d.nbDeplacements), nbPagesDeclConforme: s(d.nbPagesDeclConforme),
    nbRecherches: s(d.nbRecherches), nbSommations: s(d.nbSommations),
    inclureForfaitAdmin: !!d.inclureForfaitAdmin, inclureHonRecouvrement: !!d.inclureHonRecouvrement,
    tarifMode: (d.tarifLibreHT !== null && d.tarifLibreHT !== undefined) ? 'libre' : 'ar',
    tarifLibreHT: (d.tarifLibreHT !== null && d.tarifLibreHT !== undefined) ? s(d.tarifLibreHT) : '',
    tempsHeures: s(d.tempsHeures), tauxHoraireCharge: s(d.tauxHoraireCharge),
    fraisEnvoi: s(d.fraisEnvoi), fraisTechSI: s(d.fraisTechSI), autresCoutsVariables: s(d.autresCoutsVariables)
  };
}

function DossierForm({ editId }) {
  const state = useStore();
  const existing = editId ? state.dossiers.find(d => d.id === editId) : null;
  const [form, setForm] = useState(() => existing ? formFromDossier(existing) : emptyForm());
  const set = (k, v) => setForm(f => Object.assign({}, f, { [k]: v }));
  const setStatut = (id) => setForm(f => {
    const next = Object.assign({}, f, { statutRecouvrement: id });
    if (id === 'solde') next.montantRecouvreReel = f.montantTotalARecouvrer;
    else if (id === 'irrecouvrable') next.montantRecouvreReel = '0';
    else if (id === 'en_cours' && f.montantRecouvreReel === '') next.montantRecouvreReel = '0';
    return next;
  });
  const aCreance = M.num(form.montantTotalARecouvrer) > 0 || M.num(form.montantCreance) > 0;

  const seg = M.segmentById(form.segment);
  const isMonopole = seg && seg.monopole;
  const useLibre = !isMonopole && form.tarifMode === 'libre';
  const showActes = isMonopole || form.tarifMode === 'ar';
  const classe = M.getClasseHonoraire(M.num(form.montantCreance));

  // Objet de calcul live (MSCV.num tolere les strings)
  const calcObj = Object.assign({}, form, {
    tarifLibreHT: useLibre ? form.tarifLibreHT : null
  });

  function save(stay) {
    if (!form.donneurOrdre.trim()) { showToast('Renseignez le donneur d\'ordre', 'err'); return; }
    const n = M.num;
    const payload = {
      reference: form.reference.trim() || 'L&P-sans-ref',
      segment: form.segment, secteur: form.secteur, donneurOrdre: form.donneurOrdre.trim(),
      tags: form.tags, notes: form.notes,
      montantCreance: n(form.montantCreance), montantTotalARecouvrer: n(form.montantTotalARecouvrer),
      estCreanceEssentielle: form.estCreanceEssentielle,
      statutRecouvrement: aCreance ? (form.statutRecouvrement || 'en_cours') : null,
      montantRecouvreReel: aCreance ? n(form.montantRecouvreReel) : 0,
      nbActesSignifies: n(form.nbActesSignifies), nbAdressesSupplementaires: n(form.nbAdressesSupplementaires),
      nbUnites30min: n(form.nbUnites30min), nbDeplacements: n(form.nbDeplacements), nbPagesDeclConforme: n(form.nbPagesDeclConforme),
      nbRecherches: n(form.nbRecherches), nbSommations: n(form.nbSommations),
      inclureForfaitAdmin: form.inclureForfaitAdmin, inclureHonRecouvrement: form.inclureHonRecouvrement,
      tarifLibreHT: useLibre ? n(form.tarifLibreHT) : null,
      tempsHeures: n(form.tempsHeures), tauxHoraireCharge: n(form.tauxHoraireCharge),
      fraisEnvoi: n(form.fraisEnvoi), fraisTechSI: n(form.fraisTechSI), autresCoutsVariables: n(form.autresCoutsVariables)
    };
    if (existing) { Store.updateDossier(existing.id, payload); showToast('Dossier mis a jour'); navigate('#/dossier/' + existing.id); }
    else { const created = Store.addDossier(payload); showToast('Dossier cree'); navigate(stay ? '#/nouveau' : '#/dossier/' + created.id); }
  }

  return (
    <div className="grid" style={{ gridTemplateColumns: '1.6fr 1fr', alignItems: 'start' }}>
      <div className="card">
        {/* 1. Identification */}
        <div className="form-section">
          <h3><span className="n">1</span> Identification</h3>
          <div className="form-grid">
            <Field label="Reference"><input type="text" className="mono" value={form.reference} onChange={e => set('reference', e.target.value)} /></Field>
            <Field label="Segment">
              <select value={form.segment} onChange={e => set('segment', e.target.value)}>
                {M.SEGMENTS.map(s => <option key={s.id} value={s.id}>{s.label}</option>)}
              </select>
            </Field>
            <Field label="Secteur">
              <select value={form.secteur} onChange={e => set('secteur', e.target.value)}>
                {M.SECTEURS.map(s => <option key={s} value={s}>{s}</option>)}
              </select>
            </Field>
          </div>
          <div style={{ marginTop: 14 }}>
            <Field label="Donneur d'ordre / creancier"><input type="text" placeholder="Ex : BNP Paribas Fortis" value={form.donneurOrdre} onChange={e => set('donneurOrdre', e.target.value)} /></Field>
          </div>
          <div style={{ marginTop: 14 }}>
            <Field label="Tags"><TagEditor tags={form.tags} onChange={v => set('tags', v)} /></Field>
          </div>
          <div style={{ marginTop: 14 }}>
            <Field label="Notes"><textarea value={form.notes} onChange={e => set('notes', e.target.value)} placeholder="Commentaire libre…" /></Field>
          </div>
          <div className="pill-info" style={{ marginTop: 12 }}><span>ℹ️</span><span>{seg && seg.desc} {isMonopole ? '— Tarif reglemente (AR 2024), facturation calculee automatiquement.' : '— Hors-monopole : tarif libre possible.'}</span></div>
        </div>

        {/* 2. Creance */}
        <div className="form-section">
          <h3><span className="n">2</span> Creance</h3>
          <div className="form-grid">
            <Field label="Montant creance (principal)" hint={'Classe ' + classe.classe + ' — honoraire base ' + eur(classe.base)}>
              <NumField value={form.montantCreance} onChange={v => set('montantCreance', v)} suffix="€" />
            </Field>
            <Field label="Montant total a recouvrer" hint="principal + interets + frais">
              <NumField value={form.montantTotalARecouvrer} onChange={v => set('montantTotalARecouvrer', v)} suffix="€" />
            </Field>
          </div>
          <div style={{ marginTop: 14 }}>
            <Toggle checked={form.estCreanceEssentielle} onChange={v => set('estCreanceEssentielle', v)} label="Creance essentielle (eau / energie / hopital / telecom / scolarite) — plafond 100 € sur l'honoraire de recouvrement" />
          </div>

          {aCreance && (
            <div style={{ marginTop: 16 }}>
              <Field label="Suivi du recouvrement" hint="statut reel — le montant encaisse sert de base a l'honoraire de recouvrement">
                <div className="row" style={{ gap: 8 }}>
                  {M.STATUTS_RECOUVREMENT.map(st => (
                    <span key={st.id} className={cx('tag chip-pick', form.statutRecouvrement === st.id && 'on')}
                      onClick={() => setStatut(st.id)}
                      style={form.statutRecouvrement === st.id ? { color: st.color, borderColor: st.color + '66', background: st.color + '22' } : null}>
                      {st.label}
                    </span>
                  ))}
                </div>
              </Field>
              {(form.statutRecouvrement === 'partiel' || form.statutRecouvrement === 'en_cours') && (
                <div style={{ marginTop: 12, maxWidth: 300 }}>
                  <Field label="Montant reellement encaisse" hint="ce qui est rentre en caisse du debiteur">
                    <NumField value={form.montantRecouvreReel} onChange={v => set('montantRecouvreReel', v)} suffix="€" />
                  </Field>
                </div>
              )}
              {form.statutRecouvrement === 'solde' && <div className="muted" style={{ fontSize: 11.5, marginTop: 9 }}>Encaisse a 100 % = {eur(M.num(form.montantTotalARecouvrer))}.</div>}
              {form.statutRecouvrement === 'irrecouvrable' && <div className="muted" style={{ fontSize: 11.5, marginTop: 9 }}>Rien encaisse — honoraire de recouvrement nul.</div>}
            </div>
          )}
        </div>

        {/* 3. Tarif libre (hors-monopole) */}
        {!isMonopole && (
          <div className="form-section">
            <h3><span className="n">3</span> Mode de tarification</h3>
            <div className="row" style={{ gap: 10 }}>
              <span className={cx('tag chip-pick', form.tarifMode === 'ar' && 'on')} onClick={() => set('tarifMode', 'ar')}>Grille AR 2024</span>
              <span className={cx('tag chip-pick', form.tarifMode === 'libre' && 'on')} onClick={() => set('tarifMode', 'libre')}>Tarif libre</span>
            </div>
            {useLibre && (
              <div style={{ marginTop: 14, maxWidth: 280 }}>
                <Field label="Honoraires HT (tarif libre)"><NumField value={form.tarifLibreHT} onChange={v => set('tarifLibreHT', v)} suffix="€" /></Field>
              </div>
            )}
          </div>
        )}

        {/* 4. Actes et prestations */}
        {showActes && (
          <div className="form-section">
            <h3><span className="n">{isMonopole ? '3' : '4'}</span> Actes et prestations (AR 2024)</h3>
            <div className="form-grid">
              <Field label="Actes signifies"><NumField value={form.nbActesSignifies} onChange={v => set('nbActesSignifies', v)} /></Field>
              <Field label="Adresses suppl."><NumField value={form.nbAdressesSupplementaires} onChange={v => set('nbAdressesSupplementaires', v)} /></Field>
              <Field label="Unites de 30 min"><NumField value={form.nbUnites30min} onChange={v => set('nbUnites30min', v)} /></Field>
              <Field label="Deplacements"><NumField value={form.nbDeplacements} onChange={v => set('nbDeplacements', v)} /></Field>
              <Field label="Pages decl. conformes"><NumField value={form.nbPagesDeclConforme} onChange={v => set('nbPagesDeclConforme', v)} /></Field>
              <Field label="Recherches / attestations"><NumField value={form.nbRecherches} onChange={v => set('nbRecherches', v)} /></Field>
              <Field label="Sommations"><NumField value={form.nbSommations} onChange={v => set('nbSommations', v)} /></Field>
            </div>
            <div className="row" style={{ marginTop: 14, gap: 22 }}>
              <Toggle checked={form.inclureForfaitAdmin} onChange={v => set('inclureForfaitAdmin', v)} label="Forfait administratif (50 €)" />
              <Toggle checked={form.inclureHonRecouvrement} onChange={v => set('inclureHonRecouvrement', v)} label="Honoraire de recouvrement degressif" />
            </div>
          </div>
        )}

        {/* 5. Couts variables */}
        <div className="form-section" style={{ marginBottom: 8 }}>
          <h3><span className="n">{isMonopole ? '4' : '5'}</span> Couts variables</h3>
          <div className="form-grid">
            <Field label="Temps passe"><NumField value={form.tempsHeures} onChange={v => set('tempsHeures', v)} suffix="h" /></Field>
            <Field label="Taux horaire charge"><NumField value={form.tauxHoraireCharge} onChange={v => set('tauxHoraireCharge', v)} suffix="€/h" /></Field>
            <Field label="Frais d'envoi"><NumField value={form.fraisEnvoi} onChange={v => set('fraisEnvoi', v)} suffix="€" /></Field>
            <Field label="Frais tech / SI"><NumField value={form.fraisTechSI} onChange={v => set('fraisTechSI', v)} suffix="€" /></Field>
            <Field label="Autres couts variables"><NumField value={form.autresCoutsVariables} onChange={v => set('autresCoutsVariables', v)} suffix="€" /></Field>
          </div>
        </div>

        <div className="divider" />
        <div style={{ display: 'flex', gap: 10, justifyContent: 'flex-end' }}>
          <button className="btn btn-ghost" onClick={() => navigate(existing ? '#/dossier/' + existing.id : '#/dossiers')}>Annuler</button>
          {!existing && <button className="btn" onClick={() => save(true)}>Enregistrer + nouveau</button>}
          <button className="btn btn-accent" onClick={() => save(false)}><Icon name="check" size={15} /> {existing ? 'Enregistrer' : 'Creer le dossier'}</button>
        </div>
      </div>

      <RecapPanel obj={calcObj} sticky />
    </div>
  );
}

/* ══════════════════════ DETAIL DOSSIER ══════════════════════ */
function DossierDetail({ id }) {
  const state = useStore();
  const d = state.dossiers.find(x => x.id === id);
  const [confirm, setConfirm] = useState(false);
  if (!d) return <div className="empty">Dossier introuvable. <a className="sec" style={{ textDecoration: 'underline' }} onClick={() => navigate('#/dossiers')}>Retour a la liste</a></div>;
  const r = M.getMSCV(d);
  return (
    <div>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 14, marginBottom: 18, flexWrap: 'wrap' }}>
        <div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap' }}>
            <span className="mono" style={{ fontSize: 17, fontWeight: 600 }}>{d.reference}</span>
            <SegmentBadge id={d.segment} />
            <MscvPill taux={r.taux} />
            {M.aCreance(d) && <StatutBadge statut={d.statutRecouvrement} />}
          </div>
          <div className="sec" style={{ marginTop: 6 }}>{d.donneurOrdre} · {d.secteur} · cree le {d.dateCreation} · maj {d.dateDerniereMaj}</div>
          <div style={{ display: 'flex', gap: 5, flexWrap: 'wrap', marginTop: 10 }}>{(d.tags || []).map(t => <span key={t} className="tag">{t}</span>)}</div>
        </div>
        <div style={{ display: 'flex', gap: 8 }}>
          <button className="btn btn-ghost" onClick={() => navigate('#/dossiers')}><Icon name="back" size={15} /> Liste</button>
          <button className="btn" onClick={() => navigate('#/edit/' + d.id)}><Icon name="edit" size={15} /> Modifier</button>
          <button className="btn btn-danger" onClick={() => setConfirm(true)}><Icon name="trash" size={15} /></button>
        </div>
      </div>

      <div className="grid kpis" style={{ gridTemplateColumns: 'repeat(4,1fr)', marginBottom: 16 }}>
        <div className="kpi" style={{ '--accent': '#60a5fa' }}><div className="kpi-label">Produit HT</div><div className="kpi-value">{eur0(r.produit)}</div></div>
        <div className="kpi" style={{ '--accent': '#a78bfa' }}><div className="kpi-label">Couts variables</div><div className="kpi-value">{eur0(r.cout)}</div></div>
        <div className="kpi" style={{ '--accent': M.mscvNiveau(r.taux) === 'success' ? '#4ade80' : M.mscvNiveau(r.taux) === 'warning' ? '#f59e0b' : '#f87171' }}><div className="kpi-label">MSCV</div><div className="kpi-value" style={{ color: 'var(--color-' + M.mscvNiveau(r.taux) + ')' }}>{eur0(r.mscv)}</div></div>
        <div className="kpi" style={{ '--accent': '#E94560' }}><div className="kpi-label">Recouvrement reel</div><div className="kpi-value">{M.aCreance(d) ? M.tauxRecouvreReel(d).toFixed(0) + ' %' : '—'}</div><div className="kpi-sub">{M.aCreance(d) ? eur0(M.getEncaisse(d)) + ' encaisses' : 'sans creance'}</div></div>
      </div>

      <div className="grid" style={{ gridTemplateColumns: '1fr 1fr', alignItems: 'start' }}>
        <RecapPanel obj={d} />
        <div className="card">
          <div className="card-title">Donnees de la creance</div>
          <div className="recap-line"><span className="lbl">Montant creance (principal)</span><span className="val">{M.num(d.montantCreance) ? eur(d.montantCreance) : '—'}</span></div>
          <div className="recap-line"><span className="lbl">Classe d'honoraire</span><span className="val">{M.getClasseHonoraire(M.num(d.montantCreance)).classe}</span></div>
          <div className="recap-line"><span className="lbl">Total a recouvrer</span><span className="val">{M.num(d.montantTotalARecouvrer) ? eur(d.montantTotalARecouvrer) : '—'}</span></div>
          <div className="recap-line"><span className="lbl">Statut de recouvrement</span><span className="val">{M.aCreance(d) ? <StatutBadge statut={d.statutRecouvrement} muted /> : '—'}</span></div>
          <div className="recap-line"><span className="lbl">Montant encaisse</span><span className="val">{M.aCreance(d) ? eur(M.getEncaisse(d)) : '—'}</span></div>
          <div className="recap-line"><span className="lbl">Taux de recouvrement reel</span><span className="val">{M.aCreance(d) ? M.tauxRecouvreReel(d).toFixed(0) + ' %' : '—'}</span></div>
          <div className="recap-line"><span className="lbl">Creance essentielle</span><span className="val">{d.estCreanceEssentielle ? 'Oui (plafond 100 €)' : 'Non'}</span></div>
          {d.notes && <div style={{ marginTop: 14 }}><div className="card-title" style={{ marginBottom: 8 }}>Notes</div><div className="sec" style={{ fontSize: 13, lineHeight: 1.6 }}>{d.notes}</div></div>}
        </div>
      </div>

      <LegalFooter />

      {confirm && (
        <div className="modal-overlay" onClick={() => setConfirm(false)}>
          <div className="card modal" onClick={e => e.stopPropagation()}>
            <div style={{ fontWeight: 700, fontSize: 15, marginBottom: 8 }}>Supprimer ce dossier ?</div>
            <div className="sec" style={{ marginBottom: 18 }}>{d.reference} — {d.donneurOrdre}. Cette action est definitive.</div>
            <div style={{ display: 'flex', gap: 10, justifyContent: 'flex-end' }}>
              <button className="btn btn-ghost" onClick={() => setConfirm(false)}>Annuler</button>
              <button className="btn btn-danger" onClick={() => { Store.deleteDossier(d.id); showToast('Dossier supprime'); navigate('#/dossiers'); }}><Icon name="trash" size={15} /> Supprimer</button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

/* ══════════════════════ CALCULATEUR TARIFAIRE (standalone) ══════════════════════ */
function TarifChecker() {
  const [form, setForm] = useState(() => {
    const f = emptyForm();
    f.montantCreance = '2500'; f.montantTotalARecouvrer = '2800'; f.tauxRecouvrementEffectif = '100';
    f.nbActesSignifies = '1'; f.inclureForfaitAdmin = true; f.inclureHonRecouvrement = true;
    f.segment = 'judiciaire';
    return f;
  });
  const set = (k, v) => setForm(f => Object.assign({}, f, { [k]: v }));
  const classe = M.getClasseHonoraire(M.num(form.montantCreance));
  const calcObj = Object.assign({}, form, { tarifLibreHT: null });
  return (
    <div className="grid" style={{ gridTemplateColumns: '1.4fr 1fr', alignItems: 'start' }}>
      <div className="card">
        <div className="pill-info" style={{ marginBottom: 18 }}><span>🧮</span><span>Simulateur ad hoc, sans enregistrement. Estime un decompte AR 2024 + la MSCV avant de creer un dossier.</span></div>
        <div className="form-section">
          <h3><span className="n">A</span> Creance</h3>
          <div className="form-grid">
            <Field label="Montant creance" hint={'Classe ' + classe.classe + ' — base ' + eur(classe.base)}><NumField value={form.montantCreance} onChange={v => set('montantCreance', v)} suffix="€" /></Field>
            <Field label="Total a recouvrer"><NumField value={form.montantTotalARecouvrer} onChange={v => set('montantTotalARecouvrer', v)} suffix="€" /></Field>
            <Field label="Taux recouvrement"><NumField value={form.tauxRecouvrementEffectif} onChange={v => set('tauxRecouvrementEffectif', v)} suffix="%" /></Field>
          </div>
          <div style={{ marginTop: 12 }}><Toggle checked={form.estCreanceEssentielle} onChange={v => set('estCreanceEssentielle', v)} label="Creance essentielle (plafond 100 €)" /></div>
        </div>
        <div className="form-section">
          <h3><span className="n">B</span> Actes et prestations</h3>
          <div className="form-grid">
            <Field label="Actes signifies"><NumField value={form.nbActesSignifies} onChange={v => set('nbActesSignifies', v)} /></Field>
            <Field label="Adresses suppl."><NumField value={form.nbAdressesSupplementaires} onChange={v => set('nbAdressesSupplementaires', v)} /></Field>
            <Field label="Unites de 30 min"><NumField value={form.nbUnites30min} onChange={v => set('nbUnites30min', v)} /></Field>
            <Field label="Deplacements"><NumField value={form.nbDeplacements} onChange={v => set('nbDeplacements', v)} /></Field>
            <Field label="Pages decl. conformes"><NumField value={form.nbPagesDeclConforme} onChange={v => set('nbPagesDeclConforme', v)} /></Field>
            <Field label="Recherches"><NumField value={form.nbRecherches} onChange={v => set('nbRecherches', v)} /></Field>
            <Field label="Sommations"><NumField value={form.nbSommations} onChange={v => set('nbSommations', v)} /></Field>
          </div>
          <div className="row" style={{ marginTop: 12, gap: 22 }}>
            <Toggle checked={form.inclureForfaitAdmin} onChange={v => set('inclureForfaitAdmin', v)} label="Forfait admin (50 €)" />
            <Toggle checked={form.inclureHonRecouvrement} onChange={v => set('inclureHonRecouvrement', v)} label="Honoraire recouvrement" />
          </div>
        </div>
        <div className="form-section" style={{ marginBottom: 0 }}>
          <h3><span className="n">C</span> Couts variables (optionnel)</h3>
          <div className="form-grid">
            <Field label="Temps"><NumField value={form.tempsHeures} onChange={v => set('tempsHeures', v)} suffix="h" /></Field>
            <Field label="Taux horaire"><NumField value={form.tauxHoraireCharge} onChange={v => set('tauxHoraireCharge', v)} suffix="€/h" /></Field>
            <Field label="Frais d'envoi"><NumField value={form.fraisEnvoi} onChange={v => set('fraisEnvoi', v)} suffix="€" /></Field>
          </div>
        </div>
      </div>
      <RecapPanel obj={calcObj} sticky />
    </div>
  );
}

/* ══════════════════════ FOOTER LEGAL ══════════════════════ */
function LegalFooter() {
  return (
    <div className="legal-foot">
      Tarifs : AR du 30 novembre 1976 (civil/commercial) reforme par l'AR du 18 mai 2024 (MB 19/06/2024, en vigueur 1er octobre 2024) — art. 522 du Code judiciaire belge.
      Indexation annuelle au 1er janvier — verifier les montants indexes sur huissiersdejustice.be. Montants affiches = base 2024.
      Ce modele est un outil de pilotage, pas un avis juridique.
    </div>
  );
}

/* ══════════════════════ APP SHELL ══════════════════════ */
const NAV = [
  { name: 'dashboard', hash: '#/dashboard', label: 'Tableau de bord', icon: 'grid' },
  { name: 'dossiers', hash: '#/dossiers', label: 'Dossiers', icon: 'folder' },
  { name: 'nouveau', hash: '#/nouveau', label: 'Nouveau dossier', icon: 'plus' },
  { name: 'tarif', hash: '#/tarif', label: 'Calculateur tarifaire', icon: 'calc' }
];
const TITLES = {
  dashboard: { t: 'Tableau de bord', s: 'Vue consolidee de la rentabilite — Marge sur couts variables' },
  dossiers: { t: 'Dossiers', s: 'Liste filtrable de tous les dossiers' },
  nouveau: { t: 'Nouveau dossier', s: 'Creation — decompte tarifaire et MSCV en temps reel' },
  form: { t: 'Modifier le dossier', s: 'Edition — decompte tarifaire et MSCV en temps reel' },
  detail: { t: 'Detail du dossier', s: 'Decompte tarifaire complet et indicateurs' },
  tarif: { t: 'Calculateur tarifaire', s: 'Simulation ad hoc AR 2024 — sans enregistrement' }
};

function App() {
  const route = useRoute();
  const [filters, setFilters] = useState(EMPTY_FILTERS);
  const [menuOpen, setMenuOpen] = useState(false);
  useEffect(() => { setMenuOpen(false); }, [route.name, route.id]);
  const meta = TITLES[route.name] || TITLES.dashboard;
  const activeNav = route.name === 'detail' || route.name === 'form' ? 'dossiers' : route.name;

  let screen;
  if (route.name === 'dashboard') screen = <Dashboard filters={filters} setFilters={setFilters} />;
  else if (route.name === 'dossiers') screen = <DossiersList filters={filters} setFilters={setFilters} />;
  else if (route.name === 'nouveau') screen = <DossierForm key="new" />;
  else if (route.name === 'form') screen = <DossierForm key={route.id} editId={route.id} />;
  else if (route.name === 'detail') screen = <DossierDetail id={route.id} />;
  else if (route.name === 'tarif') screen = <TarifChecker />;
  else screen = <Dashboard filters={filters} setFilters={setFilters} />;

  return (
    <div className="app">
      <aside className={cx('sidebar', menuOpen && 'open')}>
        <div className="brand">
          <div className="brand-mark">M</div>
          <div className="brand-txt"><b>MSCV</b><span>Bespoke Operations</span></div>
        </div>
        <div className="nav-label">Pilotage</div>
        {NAV.map(n => (
          <div key={n.name} className={cx('nav-item', activeNav === n.name && 'active')} onClick={() => navigate(n.hash)}>
            <Icon name={n.icon} size={17} /> {n.label}
          </div>
        ))}
        <div className="sidebar-foot">
          <div style={{ fontWeight: 600, color: 'var(--color-text-secondary)', marginBottom: 4 }}>Etude Leroy &amp; Partners</div>
          Recouvrement de masse · Bruxelles
          <div style={{ marginTop: 10 }}>
            <span className="nav-item" style={{ padding: '6px 0', fontSize: 11.5 }} onClick={() => { if (confirm('Reinitialiser les donnees de demonstration ? Les modifications locales seront perdues.')) { Store.resetDemo(); showToast('Donnees demo restaurees'); } }}>
              <Icon name="refresh" size={13} /> Reinitialiser la demo
            </span>
          </div>
        </div>
      </aside>

      <main className="main">
        <div className="topbar">
          <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
            <button className="btn btn-ghost menu-btn" onClick={() => setMenuOpen(o => !o)}><Icon name="menu" size={17} /></button>
            <div><h1>{meta.t}</h1><div className="sub">{meta.s}</div></div>
          </div>
          {(route.name === 'dashboard' || route.name === 'dossiers') &&
            <button className="btn btn-accent" onClick={() => navigate('#/nouveau')}><Icon name="plus" size={15} /> Nouveau dossier</button>}
        </div>
        <div className="content">{screen}</div>
      </main>
      <Toasts />
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
