/* Uleez — Admin screens part 1: dashboard, lessors moderation */
const { useState: useA1, useEffect: useAE1 } = React;

// ─── AdminVolumeChart — booking volume with full-period fill + projection ──
function AdminVolumeChart({ data, height }) {
  const [chartType, setChartType] = useA1('line');
  const [hovered,   setHovered]   = useA1(null);
  const [ready,     setReady]     = useA1(false);

  useAE1(() => {
    setReady(false); setHovered(null);
    const t = setTimeout(() => setReady(true), 80);
    return () => clearTimeout(t);
  }, [data]);

  const buildFull = () => {
    const now = new Date(); now.setHours(0,0,0,0);
    const yr = now.getFullYear(), mo = now.getMonth();
    const start = new Date(yr, mo, 1), end = new Date(yr, mo + 1, 0);
    const apiMap = {};
    data.forEach(r => { apiMap[r.d] = (apiMap[r.d] || 0) + (r.value || 0); });
    const pts = [];
    const d = new Date(start);
    while (d <= end) {
      const lbl = `${String(d.getDate()).padStart(2,'0')}/${String(d.getMonth()+1).padStart(2,'0')}`;
      pts.push({ d: lbl, value: apiMap[lbl] || 0, isPast: d <= now, isToday: d.getTime() === now.getTime() });
      d.setDate(d.getDate() + 1);
    }
    return pts;
  };

  const full = buildFull();
  const n = full.length;
  let cum = 0;
  const cumFull = full.map(p => { cum += p.value; return { ...p, cumValue: cum }; });

  const todayIdx = full.findIndex(p => p.isToday);
  const pastIdx  = todayIdx >= 0 ? todayIdx : full.filter(p => p.isPast).length - 1;
  const totalSoFar = pastIdx >= 0 && cumFull[pastIdx] ? cumFull[pastIdx].cumValue : 0;
  const activeDays  = full.slice(0, pastIdx + 1).filter(p => p.value > 0).length;
  const actRate = activeDays / Math.max(1, pastIdx + 1);
  const avgActive = activeDays > 0 ? totalSoFar / activeDays : 0;
  const projDaily = avgActive * actRate;
  const hasProj = projDaily > 0 && pastIdx >= 0 && pastIdx < n - 1;

  const W = 480, H = height || 230;
  const PL = 52, PR = 14, PT = 18, PB = 34;
  const cW = W - PL - PR, cH = H - PT - PB;
  const projEnd = hasProj ? totalSoFar + projDaily * (n - 1 - pastIdx) : 0;
  const lineMaxY = Math.max(cumFull.length ? cumFull[cumFull.length-1].cumValue : 0, projEnd, 1) * 1.15;
  const barMaxY  = Math.max(...full.map(p => p.value), projDaily, 1) * 1.2;
  const sx  = i => PL + (n > 1 ? (i / (n-1)) * cW : cW/2);
  const syL = v => PT + cH - Math.min(1, Math.max(0, v / lineMaxY)) * cH;
  const syB = v => PT + cH - Math.min(1, Math.max(0, v / barMaxY))  * cH;
  const fmtY = v => v >= 1e6 ? (v/1e6).toFixed(1)+'M' : v >= 1000 ? (v/1000).toFixed(0)+'k' : String(Math.round(v));

  const bz = pts => {
    if (!pts.length) return '';
    if (pts.length === 1) return `M${pts[0].x},${pts[0].y}`;
    let d = `M${pts[0].x},${pts[0].y}`;
    for (let i = 1; i < pts.length; i++) {
      const cx = (pts[i-1].x + pts[i].x) / 2;
      d += ` C${cx},${pts[i-1].y} ${cx},${pts[i].y} ${pts[i].x},${pts[i].y}`;
    }
    return d;
  };

  const yTicksL = [0,.25,.5,.75,1].map(f => lineMaxY * f);
  const yTicksB = [0,.25,.5,.75,1].map(f => barMaxY  * f);
  const xStep = n <= 7 ? 1 : n <= 15 ? 2 : n <= 31 ? 5 : Math.ceil(n/7);

  const scaffold = (yTicks, syFn, content) => React.createElement('svg', {
    viewBox: `0 0 ${W} ${H}`, width: '100%', height: H, overflow: 'visible',
    style: { display: 'block', opacity: ready ? 1 : 0, transition: 'opacity .4s' }
  },
    yTicks.map((v, ti) => React.createElement('g', { key: ti },
      v > 0 && React.createElement('line', { x1:PL, y1:syFn(v), x2:PL+cW, y2:syFn(v), stroke:'var(--slate-100)', strokeWidth:1 }),
      React.createElement('text', { x:PL-6, y:syFn(v)+4, textAnchor:'end', fontSize:9, fill:'var(--muted)' }, fmtY(v))
    )),
    React.createElement('line', { x1:PL, y1:syFn(0), x2:PL+cW, y2:syFn(0), stroke:'var(--slate-200)', strokeWidth:1 }),
    full.map((r, i) => i % xStep === 0 && React.createElement('text', {
      key:i, x:sx(i), y:H-PB+13, textAnchor:'middle', fontSize:9, fill:'var(--muted)'
    }, r.d)),
    ...content
  );

  const renderLine = () => {
    const actPts = [];
    for (let i = 0; i <= Math.min(pastIdx, n-1); i++) {
      actPts.push({ x:sx(i), y:syL(cumFull[i].cumValue), v:cumFull[i].cumValue, d:cumFull[i].d, i });
    }
    const projPts = [];
    if (hasProj) {
      for (let i = pastIdx; i < n; i++) projPts.push({ x:sx(i), y:syL(totalSoFar + projDaily * (i - pastIdx)) });
    }
    const actPath = bz(actPts), projPath = bz(projPts);
    const f0 = actPts[0], fN = actPts[actPts.length-1];
    const actArea  = actPath && f0 ? `${actPath} L${fN.x},${syL(0)} L${f0.x},${syL(0)} Z` : '';
    const p0 = projPts[0], pN = projPts[projPts.length-1];
    const projArea = projPath && p0 ? `${projPath} L${pN.x},${syL(0)} L${p0.x},${syL(0)} Z` : '';
    const tip = hovered !== null && hovered <= pastIdx ? cumFull[hovered] : null;
    return scaffold(yTicksL, syL, [
      projArea && React.createElement('path', { key:'pra', d:projArea, fill:'#c4b5fd14' }),
      projPath && React.createElement('path', { key:'prl', d:projPath, fill:'none', stroke:'#c4b5fd', strokeWidth:2, strokeDasharray:'8,5', opacity:.85 }),
      actArea  && React.createElement('path', { key:'aca', d:actArea,  fill:'#7c3aed10' }),
      actPath  && React.createElement('path', { key:'acl', d:actPath,  fill:'none', stroke:'#7c3aed', strokeWidth:2.5, strokeLinecap:'round', strokeLinejoin:'round' }),
      pastIdx >= 0 && pastIdx < n-1 && React.createElement('line', {
        key:'sep', x1:sx(pastIdx), y1:PT, x2:sx(pastIdx), y2:syL(0), stroke:'#94a3b8', strokeWidth:1, strokeDasharray:'3,3', opacity:.55
      }),
      pastIdx >= 0 && pastIdx < n-1 && React.createElement('text', {
        key:'now', x:sx(pastIdx)+4, y:PT+10, fontSize:8.5, fill:'#94a3b8'
      }, 'Auj.'),
      ...actPts.filter(p => full[p.i] && full[p.i].value > 0).map(p => React.createElement('circle', {
        key:`d${p.i}`, cx:p.x, cy:p.y, r:hovered===p.i?5:3.5,
        fill:'#7c3aed', stroke:'#fff', strokeWidth:2,
        style:{cursor:'pointer',transition:'r .12s'},
        onMouseEnter:()=>setHovered(p.i), onMouseLeave:()=>setHovered(null),
      })),
      tip && (() => {
        const tx=sx(hovered), ty=syL(tip.cumValue);
        const bw=134, bh=50, bx=Math.min(Math.max(tx-bw/2,PL),W-bw-2), by=Math.max(ty-bh-12,PT);
        return React.createElement('g', { key:'tip', style:{pointerEvents:'none'} },
          React.createElement('rect', { x:bx,y:by,width:bw,height:bh,rx:8,fill:'#0f172a',opacity:.93 }),
          React.createElement('text', { x:bx+bw/2,y:by+17,textAnchor:'middle',fontSize:10,fill:'rgba(255,255,255,.6)' }, tip.d+' · cumul'),
          React.createElement('text', { x:bx+bw/2,y:by+36,textAnchor:'middle',fontSize:12,fill:'#c4b5fd',fontWeight:700 }, KD.fmtDA(Math.round(tip.cumValue)))
        );
      })()
    ].filter(Boolean));
  };

  const renderBar = () => {
    const bW = Math.max(4, Math.min(26, cW / Math.max(n,1) - 3));
    const tip = hovered !== null ? full[hovered] : null;
    const bars = full.map((r, i) => {
      const bx = sx(i) - bW/2, delay = i * 0.015;
      if (!r.isPast) {
        if (!projDaily) return null;
        const bh = Math.max(0, (projDaily / barMaxY) * cH);
        return React.createElement('rect', { key:i, x:bx, y:syB(projDaily), width:bW, height:bh,
          fill:'#c4b5fd', opacity:ready?0.28:0, rx:2,
          style:{transition:`opacity .4s ease ${delay}s`},
          onMouseEnter:()=>setHovered(i), onMouseLeave:()=>setHovered(null) });
      }
      if (r.value <= 0) return null;
      const bh = Math.max(0, (r.value / barMaxY) * cH);
      return React.createElement('rect', { key:i, x:bx, y:syB(r.value), width:bW, height:bh, rx:2,
        fill:'#7c3aed', opacity:ready?0.78:0,
        style:{transition:`opacity .4s ease ${delay}s`,cursor:'pointer'},
        onMouseEnter:()=>setHovered(i), onMouseLeave:()=>setHovered(null) });
    }).filter(Boolean);
    return scaffold(yTicksB, syB, [
      hasProj && React.createElement('line', { key:'pavg', x1:PL,y1:syB(projDaily),x2:PL+cW,y2:syB(projDaily),
        stroke:'#c4b5fd',strokeWidth:1.5,strokeDasharray:'6,4',opacity:.6 }),
      ...bars,
      tip && (() => {
        const isProj=!tip.isPast, tipVal=isProj?projDaily:tip.value;
        const tx=sx(hovered), ty=syB(Math.max(tipVal,0));
        const bw=124, bh=50, bx=Math.min(Math.max(tx-bw/2,PL),W-bw-2), by=Math.max(ty-bh-12,PT);
        return React.createElement('g', { key:'tip', style:{pointerEvents:'none'} },
          React.createElement('rect', { x:bx,y:by,width:bw,height:bh,rx:8,fill:'#0f172a',opacity:.93 }),
          React.createElement('text', { x:bx+bw/2,y:by+17,textAnchor:'middle',fontSize:10,fill:'rgba(255,255,255,.6)' }, tip.d+(isProj?' · estimé':'')),
          React.createElement('text', { x:bx+bw/2,y:by+36,textAnchor:'middle',fontSize:12,fill:'#c4b5fd',fontWeight:700 }, KD.fmtDA(Math.round(tipVal)))
        );
      })()
    ].filter(Boolean));
  };

  return React.createElement(React.Fragment, null,
    React.createElement('div', { style:{display:'flex',justifyContent:'flex-end',marginBottom:8} },
      React.createElement('div', { className:'seg' },
        React.createElement('button', { className:chartType==='line'?'on':'', onClick:()=>{setChartType('line');setHovered(null);} },
          React.createElement(Icon,{name:'trendUp',size:12}), 'Courbe'),
        React.createElement('button', { className:chartType==='bar'?'on':'', onClick:()=>{setChartType('bar');setHovered(null);} },
          React.createElement(Icon,{name:'barChart',size:12}), 'Barre'),
      )
    ),
    n === 0
      ? React.createElement(EmptyState,{variant:'empty',icon:'trendUp',title:'Aucune donnée',desc:'Pas encore de réservations ce mois-ci.'})
      : chartType==='bar' ? renderBar() : renderLine(),
    React.createElement('div', { style:{display:'flex',gap:14,marginTop:8,fontSize:11,color:'var(--muted)'} },
      React.createElement('span', { style:{display:'flex',alignItems:'center',gap:5} },
        React.createElement('svg',{width:20,height:8},React.createElement('line',{x1:0,y1:4,x2:20,y2:4,stroke:'#7c3aed',strokeWidth:2.5})),
        chartType==='line'?'Cumul mensuel':'Volume journalier'
      ),
      hasProj && React.createElement('span', { style:{display:'flex',alignItems:'center',gap:5} },
        React.createElement('svg',{width:20,height:8},React.createElement('line',{x1:0,y1:4,x2:20,y2:4,stroke:'#c4b5fd',strokeWidth:2,strokeDasharray:'6,4'})),
        `Projection (~${KD.fmtDA(Math.round(projDaily))}/j)`
      )
    )
  );
}

// ─── AdminFinanceChart — gross/net/commission stacked bars + cumulative lines ──
function AdminFinanceChart({ grossSeries, commissionRate, height }) {
  const [chartType, setChartType] = useA1('bar');
  const [hovered,   setHovered]   = useA1(null);
  const [ready,     setReady]     = useA1(false);

  useAE1(() => {
    setReady(false); setHovered(null);
    const t = setTimeout(() => setReady(true), 80);
    return () => clearTimeout(t);
  }, [grossSeries]);

  const rate = commissionRate > 0 ? commissionRate : 0.10;

  const buildFull = () => {
    const now = new Date(); now.setHours(0,0,0,0);
    const yr = now.getFullYear(), mo = now.getMonth();
    const start = new Date(yr, mo, 1), end = new Date(yr, mo + 1, 0);
    const apiMap = {};
    grossSeries.forEach(r => { apiMap[r.d] = (apiMap[r.d] || 0) + (r.value || 0); });
    const pts = [];
    const d = new Date(start);
    while (d <= end) {
      const lbl = `${String(d.getDate()).padStart(2,'0')}/${String(d.getMonth()+1).padStart(2,'0')}`;
      const gross = apiMap[lbl] || 0;
      pts.push({ d: lbl, gross, commission: gross*rate, net: gross*(1-rate), isPast: d <= now, isToday: d.getTime() === now.getTime() });
      d.setDate(d.getDate() + 1);
    }
    return pts;
  };

  const full = buildFull();
  const n = full.length;
  let cG=0, cN=0, cC=0;
  const cumFull = full.map(p => { cG+=p.gross; cN+=p.net; cC+=p.commission; return {...p,cumGross:cG,cumNet:cN,cumComm:cC}; });

  const todayIdx = full.findIndex(p => p.isToday);
  const pastIdx  = todayIdx >= 0 ? todayIdx : full.filter(p => p.isPast).length - 1;
  const totalGross = pastIdx >= 0 && cumFull[pastIdx] ? cumFull[pastIdx].cumGross : 0;
  const activeDays = full.slice(0, pastIdx+1).filter(p => p.gross > 0).length;
  const actRate = activeDays / Math.max(1, pastIdx+1);
  const avgActive = activeDays > 0 ? totalGross / activeDays : 0;
  const projGross = avgActive * actRate;
  const hasProj = projGross > 0 && pastIdx >= 0 && pastIdx < n-1;

  const W=480, H=height||250;
  const PL=56, PR=14, PT=18, PB=34;
  const cW=W-PL-PR, cH=H-PT-PB;
  const projEnd = hasProj ? totalGross + projGross*(n-1-pastIdx) : 0;
  const barMaxY  = Math.max(...full.map(p=>p.gross), projGross, 1) * 1.2;
  const lineMaxY = Math.max(cumFull.length?cumFull[cumFull.length-1].cumGross:0, projEnd, 1) * 1.15;

  const sx  = i => PL+(n>1?(i/(n-1))*cW:cW/2);
  const syB = v => PT+cH-Math.min(1,Math.max(0,v/barMaxY))*cH;
  const syL = v => PT+cH-Math.min(1,Math.max(0,v/lineMaxY))*cH;
  const fmtY = v => v>=1e6?(v/1e6).toFixed(1)+'M':v>=1000?(v/1000).toFixed(0)+'k':String(Math.round(v));

  const bz = pts => {
    if (!pts.length) return '';
    if (pts.length===1) return `M${pts[0].x},${pts[0].y}`;
    let d=`M${pts[0].x},${pts[0].y}`;
    for (let i=1;i<pts.length;i++) { const cx=(pts[i-1].x+pts[i].x)/2; d+=` C${cx},${pts[i-1].y} ${cx},${pts[i].y} ${pts[i].x},${pts[i].y}`; }
    return d;
  };

  const yTicksL = [0,.25,.5,.75,1].map(f=>lineMaxY*f);
  const yTicksB = [0,.25,.5,.75,1].map(f=>barMaxY*f);
  const xStep = n<=7?1:n<=15?2:n<=31?5:Math.ceil(n/7);

  const scaffold = (yTicks, syFn, content) => React.createElement('svg', {
    viewBox:`0 0 ${W} ${H}`, width:'100%', height:H, overflow:'visible',
    style:{display:'block',opacity:ready?1:0,transition:'opacity .4s'}
  },
    yTicks.map((v,ti) => React.createElement('g',{key:ti},
      v>0 && React.createElement('line',{x1:PL,y1:syFn(v),x2:PL+cW,y2:syFn(v),stroke:'var(--slate-100)',strokeWidth:1}),
      React.createElement('text',{x:PL-6,y:syFn(v)+4,textAnchor:'end',fontSize:9,fill:'var(--muted)'},fmtY(v))
    )),
    React.createElement('line',{x1:PL,y1:syFn(0),x2:PL+cW,y2:syFn(0),stroke:'var(--slate-200)',strokeWidth:1}),
    full.map((r,i) => i%xStep===0 && React.createElement('text',{key:i,x:sx(i),y:H-PB+13,textAnchor:'middle',fontSize:9,fill:'var(--muted)'},r.d)),
    ...content
  );

  // Bar mode: stacked Net (green) + Commission (amber) = Brut height
  const renderBar = () => {
    const bW = Math.max(4, Math.min(24, cW/Math.max(n,1)-3));
    const tip = hovered!==null ? full[hovered] : null;

    const bars = full.map((r,i) => {
      const bx=sx(i)-bW/2, delay=i*0.015;
      const g=r.isPast?r.gross:projGross, nn=g*(1-rate), c=g*rate;
      if (g<=0) return null;
      const netH  = Math.max(0,(nn/barMaxY)*cH);
      const commH = Math.max(0,(c/barMaxY)*cH);
      const baseY = syB(0);
      const isProj = !r.isPast;
      return React.createElement('g',{key:i,
        onMouseEnter:()=>setHovered(i), onMouseLeave:()=>setHovered(null), style:{cursor:'pointer'}},
        React.createElement('rect',{x:bx,y:baseY-netH,width:bW,height:netH,rx:0,
          fill:'#059669',opacity:ready?(isProj?0.2:0.82):0,
          style:{transition:`opacity .45s ease ${delay}s`}}),
        React.createElement('rect',{x:bx,y:baseY-netH-commH,width:bW,height:commH,rx:2,
          fill:'#f59e0b',opacity:ready?(isProj?0.2:0.85):0,
          style:{transition:`opacity .45s ease ${delay}s`}})
      );
    }).filter(Boolean);

    return scaffold(yTicksB, syB, [
      hasProj && React.createElement('line',{key:'pavg',x1:PL,y1:syB(projGross),x2:PL+cW,y2:syB(projGross),
        stroke:'#94a3b8',strokeWidth:1.5,strokeDasharray:'5,4',opacity:.5}),
      ...bars,
      tip && (() => {
        const isProj=!tip.isPast, g=isProj?projGross:tip.gross, nn=g*(1-rate), c=g*rate;
        const tx=sx(hovered), ty=syB(g);
        const bw=154, bh=78, bx=Math.min(Math.max(tx-bw/2,PL),W-bw-2), by=Math.max(ty-bh-12,PT);
        return React.createElement('g',{key:'tip',style:{pointerEvents:'none'}},
          React.createElement('rect',{x:bx,y:by,width:bw,height:bh,rx:8,fill:'#0f172a',opacity:.94}),
          React.createElement('text',{x:bx+bw/2,y:by+15,textAnchor:'middle',fontSize:9.5,fill:'rgba(255,255,255,.5)'},tip.d+(isProj?' · estimé':'')),
          React.createElement('text',{x:bx+10,y:by+32,fontSize:10,fill:'#6ee7b7'},'■ Net loueurs'),
          React.createElement('text',{x:bx+bw-10,y:by+32,textAnchor:'end',fontSize:10,fill:'#6ee7b7',fontWeight:700},KD.fmtDA(Math.round(nn))),
          React.createElement('text',{x:bx+10,y:by+50,fontSize:10,fill:'#fcd34d'},'■ Commission'),
          React.createElement('text',{x:bx+bw-10,y:by+50,textAnchor:'end',fontSize:10,fill:'#fcd34d',fontWeight:700},KD.fmtDA(Math.round(c))),
          React.createElement('text',{x:bx+10,y:by+68,fontSize:10,fill:'rgba(255,255,255,.75)'},'Total brut'),
          React.createElement('text',{x:bx+bw-10,y:by+68,textAnchor:'end',fontSize:10,fill:'#fff',fontWeight:700},KD.fmtDA(Math.round(g)))
        );
      })()
    ].filter(Boolean));
  };

  // Line mode: cumulative Brut (green) + Net (violet), commission = amber fill between
  const renderLine = () => {
    const brutPts=[], netPts=[];
    for (let i=0; i<=Math.min(pastIdx,n-1); i++) {
      brutPts.push({x:sx(i),y:syL(cumFull[i].cumGross),v:cumFull[i].cumGross,d:cumFull[i].d,i});
      netPts.push( {x:sx(i),y:syL(cumFull[i].cumNet),                                         i});
    }
    const projBrut=[], projNet=[];
    if (hasProj) {
      for (let i=pastIdx; i<n; i++) {
        const dO=i-pastIdx;
        projBrut.push({x:sx(i),y:syL(totalGross+projGross*dO)});
        projNet.push( {x:sx(i),y:syL(totalGross*(1-rate)+projGross*(1-rate)*dO)});
      }
    }
    const brutPath=bz(brutPts), netPath=bz(netPts);
    const pBrutPath=bz(projBrut), pNetPath=bz(projNet);
    const f0=brutPts[0], fN=brutPts[brutPts.length-1];
    const brutArea = brutPath&&f0 ? `${brutPath} L${fN.x},${syL(0)} L${f0.x},${syL(0)} Z` : '';
    // Commission fill: polygon from brut line to net line (reversed)
    const commFill = brutPts.length>1 ? (() => {
      let dp=`M${brutPts[0].x},${brutPts[0].y}`;
      for (let i=1;i<brutPts.length;i++){const cx=(brutPts[i-1].x+brutPts[i].x)/2;dp+=` C${cx},${brutPts[i-1].y} ${cx},${brutPts[i].y} ${brutPts[i].x},${brutPts[i].y}`;}
      const rev=[...netPts].reverse();
      dp+=` L${rev[0].x},${rev[0].y}`;
      for (let i=1;i<rev.length;i++){const cx=(rev[i-1].x+rev[i].x)/2;dp+=` C${cx},${rev[i-1].y} ${cx},${rev[i].y} ${rev[i].x},${rev[i].y}`;}
      return dp+' Z';
    })() : '';
    const tip = hovered!==null && hovered<=pastIdx ? cumFull[hovered] : null;
    return scaffold(yTicksL, syL, [
      brutArea && React.createElement('path',{key:'ba',d:brutArea,fill:'#05966909'}),
      commFill && React.createElement('path',{key:'cf',d:commFill,fill:'#f59e0b20'}),
      pBrutPath && React.createElement('path',{key:'pbl',d:pBrutPath,fill:'none',stroke:'#059669',strokeWidth:2,strokeDasharray:'8,5',opacity:.6}),
      pNetPath  && React.createElement('path',{key:'pnl',d:pNetPath, fill:'none',stroke:'#7c3aed',strokeWidth:2,strokeDasharray:'8,5',opacity:.6}),
      netPath   && React.createElement('path',{key:'nl',d:netPath,  fill:'none',stroke:'#7c3aed',strokeWidth:2,  strokeLinecap:'round',strokeLinejoin:'round'}),
      brutPath  && React.createElement('path',{key:'bl',d:brutPath, fill:'none',stroke:'#059669',strokeWidth:2.5,strokeLinecap:'round',strokeLinejoin:'round'}),
      pastIdx>=0&&pastIdx<n-1 && React.createElement('line',{key:'sep',x1:sx(pastIdx),y1:PT,x2:sx(pastIdx),y2:syL(0),stroke:'#94a3b8',strokeWidth:1,strokeDasharray:'3,3',opacity:.5}),
      pastIdx>=0&&pastIdx<n-1 && React.createElement('text',{key:'now',x:sx(pastIdx)+4,y:PT+10,fontSize:8.5,fill:'#94a3b8'},'Auj.'),
      ...brutPts.filter(p=>full[p.i]&&full[p.i].gross>0).map(p=>React.createElement('circle',{
        key:`d${p.i}`,cx:p.x,cy:p.y,r:hovered===p.i?5:3,fill:'#059669',stroke:'#fff',strokeWidth:2,
        style:{cursor:'pointer',transition:'r .12s'},
        onMouseEnter:()=>setHovered(p.i), onMouseLeave:()=>setHovered(null),
      })),
      tip && (() => {
        const tx=sx(hovered), ty=syL(tip.cumGross);
        const bw=162, bh=78, bx=Math.min(Math.max(tx-bw/2,PL),W-bw-2), by=Math.max(ty-bh-12,PT);
        return React.createElement('g',{key:'tip',style:{pointerEvents:'none'}},
          React.createElement('rect',{x:bx,y:by,width:bw,height:bh,rx:8,fill:'#0f172a',opacity:.94}),
          React.createElement('text',{x:bx+bw/2,y:by+15,textAnchor:'middle',fontSize:9.5,fill:'rgba(255,255,255,.5)'},tip.d+' · cumul'),
          React.createElement('text',{x:bx+10,y:by+32,fontSize:10,fill:'#6ee7b7'},'■ Net loueurs'),
          React.createElement('text',{x:bx+bw-10,y:by+32,textAnchor:'end',fontSize:10,fill:'#6ee7b7',fontWeight:700},KD.fmtDA(Math.round(tip.cumNet))),
          React.createElement('text',{x:bx+10,y:by+50,fontSize:10,fill:'#fcd34d'},'■ Commission'),
          React.createElement('text',{x:bx+bw-10,y:by+50,textAnchor:'end',fontSize:10,fill:'#fcd34d',fontWeight:700},KD.fmtDA(Math.round(tip.cumComm))),
          React.createElement('text',{x:bx+10,y:by+68,fontSize:10,fill:'rgba(255,255,255,.75)'},'Brut total'),
          React.createElement('text',{x:bx+bw-10,y:by+68,textAnchor:'end',fontSize:10,fill:'#fff',fontWeight:700},KD.fmtDA(Math.round(tip.cumGross)))
        );
      })()
    ].filter(Boolean));
  };

  return React.createElement(React.Fragment, null,
    React.createElement('div', { style:{display:'flex',justifyContent:'flex-end',marginBottom:8} },
      React.createElement('div', { className:'seg' },
        React.createElement('button',{className:chartType==='line'?'on':'',onClick:()=>{setChartType('line');setHovered(null);}},React.createElement(Icon,{name:'trendUp',size:12}),'Courbe'),
        React.createElement('button',{className:chartType==='bar'?'on':'',onClick:()=>{setChartType('bar');setHovered(null);}},React.createElement(Icon,{name:'barChart',size:12}),'Barre'),
      )
    ),
    n===0
      ? React.createElement(EmptyState,{variant:'empty',icon:'banknote',title:'Aucune donnée',desc:'Pas de transactions ce mois-ci.'})
      : chartType==='bar' ? renderBar() : renderLine(),
    React.createElement('div',{style:{display:'flex',gap:14,marginTop:8,fontSize:11,color:'var(--muted)',flexWrap:'wrap'}},
      React.createElement('span',{style:{display:'flex',alignItems:'center',gap:5}},
        React.createElement('span',{style:{width:10,height:10,borderRadius:2,background:'#059669',display:'inline-block'}}), 'Net loueurs'),
      React.createElement('span',{style:{display:'flex',alignItems:'center',gap:5}},
        React.createElement('span',{style:{width:10,height:10,borderRadius:2,background:'#f59e0b',display:'inline-block'}}), 'Commission Uleez'),
      hasProj && React.createElement('span',{style:{display:'flex',alignItems:'center',gap:5}},
        React.createElement('svg',{width:20,height:8},React.createElement('line',{x1:0,y1:4,x2:20,y2:4,stroke:'#94a3b8',strokeWidth:1.5,strokeDasharray:'5,4'})),
        `Projection (~${KD.fmtDA(Math.round(projGross))}/j)`)
    )
  );
}

// ============================================================
// ADMIN DASHBOARD
// ============================================================
// Les trois périodes EN COURS, côte à côte. Toutes « en cours » à dessein : mélanger une
// période close avec deux périodes vivantes rendrait les tuiles incomparables entre elles.
const REVENUE_TILES = [
  ['month',   'Mois en cours',      '#059669'],
  ['quarter', 'Trimestre en cours', '#0284c7'],
  ['year',    'Année en cours',     '#7c3aed'],
];

// Les trois sources de revenu d'Uleez. L'ordre EST l'ordre d'empilement des barres, de bas
// en haut ; les mêmes couleurs que la ventilation en tuiles, pour que l'œil fasse le lien.
const REVENUE_SOURCES = [
  ['commission',   'Commissions', '#059669'],
  ['serviceFee',   'Frais',       '#0284c7'],
  ['subscription', 'Abonnements', '#7c3aed'],
];

// ─── RevenueSourceChart — d'où vient l'argent, mois par mois ───────────────
//
// Barres EMPILÉES : la hauteur totale répond à « combien ce mois-là », les segments à
// « d'où ». Trois courbes superposées auraient répondu à la seconde question en rendant la
// première illisible.
//
// La remise financée n'est pas un segment : c'est un COÛT, elle se soustrait. L'empiler la
// ferait passer pour un revenu. Elle est signalée par un liseré rose sous l'axe quand elle
// existe, et le total imprimé dans l'infobulle en tient compte.
//
// Un clic sur une barre déplace l'ancre de tout l'écran : c'est le geste naturel après avoir
// repéré un mois qui sort de l'ordinaire.
function RevenueSourceChart({ buckets, activeKey, onSelect, height }) {
  const [hovered, setHovered] = useA1(null);

  const W = 520, H = height || 210;
  const PL = 54, PR = 12, PT = 14, PB = 30;
  const cW = W - PL - PR, cH = H - PT - PB;

  const totals = buckets.map(b => REVENUE_SOURCES.reduce((s, [k]) => s + Math.max(0, b[k] || 0), 0));
  // Échelle sur le maximum réel, jamais sur zéro : une série entièrement vide diviserait par 0.
  const max = Math.max(1, ...totals);
  const n = buckets.length || 1;
  const slot = cW / n;
  const bw = Math.min(30, slot * 0.62);
  const y = (v) => PT + cH - (v / max) * cH;

  const ticks = [0, 0.5, 1].map(f => Math.round(max * f));

  return (
    <div style={{ position: 'relative' }}>
      <svg viewBox={`0 0 ${W} ${H}`} style={{ width: '100%', height: H, display: 'block', overflow: 'visible' }}>
        {ticks.map((t, i) => {
          const yy = y(t);
          return (
            <g key={i}>
              <line x1={PL} x2={W - PR} y1={yy} y2={yy} stroke="var(--line)" strokeDasharray={i === 0 ? '' : '3 4'} />
              <text x={PL - 8} y={yy + 3.5} textAnchor="end" fontSize="9.5" fill="var(--muted)">
                {t >= 1000 ? `${Math.round(t / 1000)}k` : t}
              </text>
            </g>
          );
        })}

        {buckets.map((b, i) => {
          const x = PL + i * slot + (slot - bw) / 2;
          const isActive = b.key === activeKey;
          const isHover = hovered === i;
          let acc = 0;
          return (
            <g key={b.key}
              onMouseEnter={() => setHovered(i)}
              onMouseLeave={() => setHovered(null)}
              onClick={() => onSelect && onSelect(b.key)}
              style={{ cursor: onSelect ? 'pointer' : 'default' }}>
              {/* Zone de survol pleine hauteur : viser une barre de 3 px de haut, sur un mois
                  presque vide, serait impossible autrement. */}
              <rect x={PL + i * slot} y={PT} width={slot} height={cH} fill="transparent" />
              {REVENUE_SOURCES.map(([key, , color]) => {
                const v = Math.max(0, b[key] || 0);
                if (v <= 0) return null;
                const h = (v / max) * cH;
                acc += h;
                return (
                  <rect key={key} x={x} y={PT + cH - acc} width={bw} height={h}
                    fill={color}
                    opacity={isActive || isHover ? 1 : 0.78}
                    rx={1.5} />
                );
              })}
              {b.discount > 0 && (
                <rect x={x} y={PT + cH + 2} width={bw} height={2.5} fill="#e11d48" rx={1} />
              )}
              <text x={x + bw / 2} y={H - 10} textAnchor="middle" fontSize="9.5"
                fill={isActive ? 'var(--ink)' : 'var(--muted)'}
                fontWeight={isActive ? 800 : 500}>
                {b.shortLabel}
              </text>
            </g>
          );
        })}
      </svg>

      {hovered != null && buckets[hovered] && (
        <div style={{
          position: 'absolute', top: 0, left: `${((hovered + 0.5) / n) * 100}%`,
          transform: 'translateX(-50%)', pointerEvents: 'none',
          background: 'var(--surface)', border: '1px solid var(--line)', borderRadius: 10,
          padding: '8px 11px', boxShadow: '0 6px 18px rgba(0,0,0,.10)', whiteSpace: 'nowrap', zIndex: 3,
        }}>
          <div style={{ fontSize: 12, fontWeight: 800, marginBottom: 4 }}>{buckets[hovered].label}</div>
          {REVENUE_SOURCES.map(([key, label, color]) => (
            <div key={key} style={{ fontSize: 11.5, display: 'flex', gap: 8, justifyContent: 'space-between' }}>
              <span style={{ color }}>{label}</span>
              <strong>{KD.fmtDA(buckets[hovered][key] || 0)}</strong>
            </div>
          ))}
          {buckets[hovered].discount > 0 && (
            <div style={{ fontSize: 11.5, display: 'flex', gap: 8, justifyContent: 'space-between' }}>
              <span style={{ color: '#e11d48' }}>Remises</span>
              <strong style={{ color: '#e11d48' }}>−{KD.fmtDA(buckets[hovered].discount)}</strong>
            </div>
          )}
          <div style={{ fontSize: 12, display: 'flex', gap: 10, justifyContent: 'space-between',
                        borderTop: '1px solid var(--line)', marginTop: 5, paddingTop: 4 }}>
            <span className="muted">Total</span>
            <strong>{KD.fmtDA(buckets[hovered].total || 0)}</strong>
          </div>
        </div>
      )}
    </div>
  );
}

function AdminDashboard({ navigate, params, setParams }) {
  const [stats, setStats] = useA1(null);
  const [pendingLessors, setPendingLessors] = useA1([]);
  // Renters whose identity documents await a decision. The list is the first page only
  // (the card shows a handful); the real total comes from the dashboard counter, so the
  // badge stays truthful past the page size.
  const [pendingCustomers, setPendingCustomers] = useA1([]);
  const [finance, setFinance] = useA1(null);
  const [mapData, setMapData] = useA1([]);
  const [series, setSeries] = useA1(null);
  const [loading, setLoading] = useA1(true);
  const [err, setErr] = useA1(false);
  const toast = useToast();

  // L'ancre vit dans la ROUTE (#dashboard~{"anchor":"2026-07"}) : un rechargement ou un lien
  // partagé conserve le mois consulté. Elle vient donc de l'utilisateur, et est validée avant
  // d'atteindre l'API — `anchorToISO` renvoie undefined sur toute valeur hors AAAA-MM.
  const anchor = /^\d{4}-(0[1-9]|1[0-2])$/.test(params?.anchor || '') ? params.anchor : null;
  const setAnchor = (a) => setParams(a ? { ...params, anchor: a } : (({ anchor, ...rest }) => rest)(params || {}));

  useAE1(() => {
    let cancelled = false;
    setLoading(true);
    setErr(false);
    const date = anchorToISO(anchor);

    Promise.all([
      API.admin.getDashboard('month', date).catch(() => null),
      API.admin.getLessors('pending').catch(() => []),
      API.admin.getFinanceSummary('month', date).catch(() => null),
      API.admin.getLessorsMap().catch(() => []),
      API.admin.getCustomers({ status: 'kyc_pending', limit: 5 }).catch(() => null),
      API.admin.getRevenueSeries({ granularity: 'month', anchor, span: 12 }).catch(() => null),
    ]).then(([dash, pending, fin, map, kyc, rev]) => {
      if (cancelled) return;
      setStats(dash);
      setPendingLessors(Array.isArray(pending) ? pending.map(API.normalizeLessor) : []);
      setFinance(fin);
      setMapData(Array.isArray(map) ? map : []);
      setSeries(rev);
      setPendingCustomers((kyc?.items || []).map(u => ({
        id: u.id,
        name: `${u.firstName || ''} ${u.lastName || ''}`.trim() || 'Client',
        phone: u.phone || '',
        avatar: u.avatarUrl ? API.resolveUrl(u.avatarUrl) : null,
        date: u.createdAt ? new Date(u.createdAt).toLocaleDateString('fr-FR', { day: 'numeric', month: 'short', year: 'numeric' }) : '',
      })));
      setLoading(false);
    }).catch(() => {
      if (!cancelled) { setErr(true); setLoading(false); }
    });

    return () => { cancelled = true; };
  }, [anchor]);

  const today = new Date().toLocaleDateString('fr-FR', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' });
  // Libellé RENVOYÉ PAR L'API, pas « ce mois » en dur : un écran qui montre juillet ne doit
  // pas prétendre parler du mois courant.
  const periodLabel = stats?.period?.label || anchorLabel(anchor);
  const isCurrentPeriod = stats?.period?.isCurrent !== false;

  // Authoritative count (the list above is capped at 5), falls back to the list length if
  // the dashboard call was the one that failed.
  const pendingKycTotal = stats?.pendingKycCustomers ?? pendingCustomers.length;

  // `GET /admin/dashboard` porte déjà l'agrégat de revenus : pas d'appel supplémentaire.
  const revenue = stats?.revenue || null;

  const revSeries = (stats?.revenueSeries || []).map(r => ({
    d: r.d || r.label,
    value: typeof r.value === 'number' ? r.value : Number(r.value || r.v || 0)
  }));

  // Taux mélangé, déduit de la base réellement commissionnée (la location), pas du total
  // réglé par le client qui inclut les frais de service et retranche les remises.
  const commissionBase = (finance?.byLessor || []).reduce((s, l) => s + (l.gross || 0), 0);
  const commissionRate = commissionBase > 0
    ? (finance.commissionTotal || 0) / commissionBase
    : 0.10;

  return (
    <div className="page-wide">
      <div className="toolbar">
        <div className="page-greeting">
          <h2>Vue d'ensemble de la plateforme</h2>
          <div className="gdate">{today}</div>
        </div>
      </div>

      {/* L'ancre pilote TOUT l'écran : tuiles, tendances, revenus, graphique. Séparer les
          commandes ferait cohabiter deux mois différents sur la même page. */}
      <PeriodNavigator
        anchor={anchor}
        onChange={setAnchor}
        disabled={loading}
        sub="Tendances, revenus et volume de la période"
      />

      <div className="stat-grid" style={{ marginBottom: 20 }}>
        {loading
          ? [0,1,2,3].map(i => <div key={i} className="statcard"><Sk w={46} h={46} r={13} /><Sk w="60%" h={26} style={{ marginTop: 18 }} /><Sk w="80%" h={13} style={{ marginTop: 10 }} /></div>)
          : err
            ? <div className="card card-pad" style={{ gridColumn: '1/-1' }}><EmptyState variant="err" title="Impossible de charger le tableau de bord" action={<Button variant="secondary" icon="refresh" onClick={() => { setLoading(true); setErr(false); API.admin.getDashboard().then(d => { setStats(d); setLoading(false); }).catch(() => { setErr(true); setLoading(false); }); }}>Réessayer</Button>} /></div>
            : <>
              <StatCard icon="users" color="blue"
                value={stats?.activeLessors ?? '—'}
                label="Loueurs actifs"
                meta={[
                  `${pendingLessors.length} en attente`,
                  stats?.suspendedLessors ? `${stats.suspendedLessors} suspendus` : null,
                ].filter(Boolean).join(' · ')}
                trend={stats?.trends?.lessors} />
              <StatCard icon="car" color="green"
                value={stats?.publishedVehicles ?? '—'}
                label="Véhicules en ligne"
                // « en ligne » = réellement cherchable : publié, validé, non suspendu, et
                // dont le loueur est approuvé.
                meta={[
                  `${stats?.vehiclesUnderReview ?? 0} en révision`,
                  stats?.suspendedVehicles ? `${stats.suspendedVehicles} suspendus` : null,
                ].filter(Boolean).join(' · ')}
                trend={stats?.trends?.vehicles} />
              <StatCard icon="clipboard" color="violet"
                value={stats?.totalBookings ?? '—'}
                label="Réservations"
                meta={periodLabel}
                trend={stats?.trends?.bookings} />
              <StatCard icon="banknote" color="amber"
                value={stats?.totalVolume ? KD.fmtNum(Math.round(stats.totalVolume / 1000)) + ' k' : '—'}
                label="Volume (DA)"
                meta={stats?.totalCommission ? `Commission : ${KD.fmtDA(stats.totalCommission)}` : 'Commission'}
                trend={stats?.trends?.volume} />
            </>}
      </div>

      {/* ── Ce qu'ULEEZ perçoit ────────────────────────────────────────────────
          Le reste du tableau de bord est orienté « ce que la plateforme fait circuler »
          (volume, réservations, ce que touchent les loueurs). Ce bloc-ci répond à la seule
          question qui concerne Uleez : combien j'encaisse, et d'où ça vient. */}
      <Card style={{ marginBottom: 20 }}>
        <CardHead title="Revenus Uleez" sub="Commissions + frais clients + abonnements loueurs"
          icon={{ name: 'banknote', color: 'green' }}
          right={<Button size="sm" variant="ghost" iconRight="chevRight" onClick={() => navigate('billing')}>Facturation</Button>} />
        <div className="card-pad">
          {loading
            ? <div className="stat-grid">{[0,1,2].map(i => <div key={i} className="statcard"><Sk w="60%" h={26} /><Sk w="80%" h={13} style={{ marginTop: 10 }} /></div>)}</div>
            : !revenue
              ? <EmptyState variant="err" title="Revenus indisponibles" desc="Le calcul des revenus n'a pas pu être chargé." />
              : <>
                <div className="stat-grid">
                  {REVENUE_TILES.map(([key, title, color]) => {
                    const p = revenue.periods?.[key];
                    if (!p) return null;
                    return (
                      <div key={key} className="statcard" style={{ borderLeft: `3px solid ${color}` }}>
                        <div className="row between" style={{ alignItems: 'flex-start' }}>
                          <div className="muted" style={{ fontSize: 12, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '.5px' }}>
                            {title}
                          </div>
                          {p.delta && (
                            <Badge cls={p.delta.dir === 'up' ? 'green' : 'rose'}>
                              {p.delta.value >= 0 ? '+' : ''}{p.delta.value}%
                            </Badge>
                          )}
                        </div>
                        <div style={{ fontSize: 24, fontWeight: 800, marginTop: 6, color }}>{KD.fmtDA(p.total)}</div>
                        <div className="muted" style={{ fontSize: 11.5, marginTop: 5, lineHeight: 1.5 }}>
                          {p.label}
                          {p.previousPeriodTotal > 0 && <> · précédent {KD.fmtDA(p.previousPeriodTotal)}</>}
                        </div>
                        {/* La distinction qui compte : le revenu est ACQUIS dès l'encaissement
                            de la location, mais n'est RECOUVRÉ qu'à la facture du mois suivant.
                            Une tuile qui ne montrerait que l'un des deux serait trompeuse. */}
                        <div style={{ marginTop: 8, paddingTop: 8, borderTop: '1px solid var(--line)', fontSize: 11.5, lineHeight: 1.6 }}>
                          <div><span className="muted">déjà réglé</span> <strong>{KD.fmtDA(p.settled)}</strong></div>
                          <div><span className="muted">à facturer</span> <strong style={{ color: 'var(--amber-700)' }}>{KD.fmtDA(p.pending)}</strong></div>
                        </div>
                      </div>
                    );
                  })}
                </div>

                {/* Ventilation par source sur le mois : les 3 revenus d'Uleez, nommés. */}
                {revenue.periods?.month && (
                  <div style={{ marginTop: 16 }}>
                    <div className="muted" style={{ fontSize: 12, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '.5px', marginBottom: 8 }}>
                      Origine des revenus — {revenue.periods.month.label}
                    </div>
                    <div className="grid-2" style={{ gridTemplateColumns: 'repeat(auto-fit,minmax(180px,1fr))', gap: 10 }}>
                      {[
                        ['Commissions loueurs', revenue.periods.month.bySource?.commission, '#059669'],
                        ['Frais de service clients', revenue.periods.month.bySource?.serviceFee, '#0284c7'],
                        ['Abonnements loueurs', revenue.periods.month.bySource?.subscription, '#7c3aed'],
                        ['Remises financées', -(revenue.periods.month.discount || 0), '#e11d48'],
                      ].map(([label, val, c]) => (
                        // `.roi-tile` (colonne) et non `.kv` (ligne clé-valeur) : `.kv` posait
                        // le libellé et le montant côte à côte, top-alignés malgré deux corps
                        // très différents. Même correctif que l'écran Abonnement loueur.
                        <div key={label} className="roi-tile" style={{ padding: '10px 12px', borderRadius: 10 }}>
                          <div className="muted roi-label">{label}</div>
                          <div className="roi-value" style={{ fontSize: 17, color: c }}>{KD.fmtDA(val || 0)}</div>
                        </div>
                      ))}
                    </div>
                    <div className="muted" style={{ fontSize: 11, marginTop: 8 }}>
                      {revenue.formula}
                      {revenue.recognition === 'prorata' && ' · abonnements étalés sur la période couverte'}
                    </div>
                  </div>
                )}

                {/* Les mêmes trois sources, mois par mois. C'est ici que les abonnements
                    deviennent VISIBLES dans la durée : les tuiles ne montrent qu'un instant,
                    et un revenu récurrent ne se lit que sur une série.
                    Les valeurs viennent du même calcul que les tuiles côté serveur, donc la
                    dernière barre égale toujours la tuile du mois. */}
                {series?.buckets?.length > 0 && (
                  <div style={{ marginTop: 20, paddingTop: 16, borderTop: '1px solid var(--line)' }}>
                    <div className="row between" style={{ marginBottom: 10, alignItems: 'baseline' }}>
                      <div className="muted" style={{ fontSize: 12, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '.5px' }}>
                        Douze mois de revenus
                      </div>
                      <div style={{ display: 'flex', gap: 14, fontSize: 11.5 }}>
                        {REVENUE_SOURCES.map(([key, label, color]) => (
                          <span key={key} style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>
                            <span style={{ width: 9, height: 9, borderRadius: 3, background: color, display: 'inline-block' }} />
                            <span className="muted">{label}</span>
                          </span>
                        ))}
                      </div>
                    </div>
                    {React.createElement(RevenueSourceChart, {
                      buckets: series.buckets,
                      activeKey: anchor || series.buckets[series.buckets.length - 1]?.key,
                      onSelect: setAnchor,
                      height: 210,
                    })}
                  </div>
                )}
              </>}
        </div>
      </Card>

      <Card style={{ marginBottom: 20 }}>
        <CardHead title="Volume de réservations" sub={isCurrentPeriod ? periodLabel + ' · en cours' : periodLabel} icon={{ name: 'trendUp', color: 'violet' }}
          right={stats?.trends?.bookings ? React.createElement(Badge, { cls: stats.trends.bookings.dir === 'up' ? 'violet' : 'rose' }, stats.trends.bookings.value) : null} />
        <div className="card-pad">
          {loading
            ? <Sk w="100%" h={230} r={12} />
            : err
              ? <EmptyState variant="err" title="Erreur de chargement du graphique" />
              : React.createElement(AdminVolumeChart, { data: revSeries, height: 230 })}
        </div>
      </Card>

      {/* Modération : les deux files d'attente côte à côte (loueurs et clients). */}
      <div className="grid-2" style={{ gridTemplateColumns: '1fr 1fr', marginBottom: 20 }}>
        <Card>
          <CardHead title="Loueurs à approuver"
            sub={loading ? '…' : `${pendingLessors.length} en attente`}
            icon={{ name: 'shield', color: 'amber' }}
            right={<Button size="sm" variant="ghost" iconRight="arrowUpRight" onClick={() => navigate('lessors')}>Tout voir</Button>} />
          <div className="card-pad" style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
            {loading && [0,1,2].map(i => <div key={i} style={{ display: 'flex', gap: 11, padding: '8px 0' }}><Sk w={40} h={40} r={99} /><div style={{ flex: 1 }}><Sk w="50%" h={13} /><Sk w="70%" h={11} style={{ marginTop: 7 }} /></div></div>)}
            {!loading && err && <EmptyState variant="err" title="Erreur" />}
            {!loading && !err && pendingLessors.length === 0 && <EmptyState variant="ok" icon="check" title="Aucune demande" desc="Tous les loueurs sont traités." />}
            {!loading && !err && pendingLessors.map(l => (
              <div key={l.id} className="req" style={{ padding: 13, cursor: 'pointer' }} onClick={() => navigate('lessors')}>
                <Avatar name={l.name} size={42} square />
                <div className="req-body">
                  <div className="req-top"><span className="n" style={{ fontSize: 14 }}>{l.name}</span></div>
                  <div className="req-meta" style={{ marginTop: 4 }}>
                    <Icon name="pin" size={13} />{l.wilaya}<span className="sep" />{l.type}<span className="sep" />{l.date}
                  </div>
                </div>
                <Icon name="chevRight" size={18} className="muted" />
              </div>
            ))}
          </div>
        </Card>
        <Card>
          <CardHead title="Clients à approuver"
            sub={loading ? '…' : `${pendingKycTotal} en attente de vérification`}
            icon={{ name: 'shield', color: 'blue' }}
            right={<Button size="sm" variant="ghost" iconRight="arrowUpRight" onClick={() => navigate('clients', { tab: 'kyc_pending' })}>Tout voir</Button>} />
          <div className="card-pad" style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
            {loading && [0,1,2].map(i => <div key={i} style={{ display: 'flex', gap: 11, padding: '8px 0' }}><Sk w={40} h={40} r={99} /><div style={{ flex: 1 }}><Sk w="50%" h={13} /><Sk w="70%" h={11} style={{ marginTop: 7 }} /></div></div>)}
            {!loading && err && <EmptyState variant="err" title="Erreur" />}
            {!loading && !err && pendingCustomers.length === 0 && <EmptyState variant="ok" icon="check" title="Aucune demande" desc="Toutes les identités sont traitées." />}
            {!loading && !err && pendingCustomers.map(c => (
              <div key={c.id} className="req" style={{ padding: 13, cursor: 'pointer' }} onClick={() => navigate('clients', { tab: 'kyc_pending' })}>
                <Avatar name={c.name} src={c.avatar} size={42} />
                <div className="req-body">
                  <div className="req-top"><span className="n" style={{ fontSize: 14 }}>{c.name}</span></div>
                  <div className="req-meta" style={{ marginTop: 4 }}>
                    <Icon name="phone" size={13} />{c.phone || '—'}<span className="sep" />{c.date}
                  </div>
                </div>
                <Icon name="chevRight" size={18} className="muted" />
              </div>
            ))}
          </div>
        </Card>
      </div>

      <Card style={{ marginBottom: 20 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '14px 18px', borderBottom: '1px solid var(--line)', flexWrap: 'wrap', rowGap: 8 }}>
          <div className="sc-ico amber" style={{ width: 36, height: 36, borderRadius: 10 }}>
            <Icon name="banknote" size={18} />
          </div>
          <div>
            <div style={{ fontSize: 14, fontWeight: 800, color: 'var(--ink)' }}>Santé économique de la plateforme</div>
            <div style={{ fontSize: 12, color: 'var(--muted)' }}>Répartition brut · commission · net — mois en cours</div>
          </div>
          <div className="spacer" />
          <Button size="sm" variant="ghost" iconRight="arrowUpRight" onClick={() => navigate('activity')}>Activité globale</Button>
        </div>
        <div className="card-pad">
          {loading
            ? <><Sk w="100%" h={60} r={12} style={{ marginBottom: 16 }} /><Sk w="100%" h={240} r={12} /></>
            : <>
                <div className="stat-grid" style={{ marginBottom: 20 }}>
                  <div className="statcard" style={{ borderLeft: '3px solid #059669' }}>
                    <div className="muted" style={{ fontSize: 12, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '.5px' }}>Revenu brut</div>
                    <div style={{ fontSize: 22, fontWeight: 800, marginTop: 6, color: '#059669' }}>{KD.fmtDA(finance?.grossTotal || 0)}</div>
                    <div className="muted" style={{ fontSize: 11, marginTop: 3 }}>Total perçu par la plateforme</div>
                  </div>
                  <div className="statcard" style={{ borderLeft: '3px solid #f59e0b' }}>
                    <div className="muted" style={{ fontSize: 12, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '.5px' }}>Commission Uleez</div>
                    <div style={{ fontSize: 22, fontWeight: 800, marginTop: 6, color: '#d97706' }}>{KD.fmtDA(finance?.commissionTotal || 0)}</div>
                    <div className="muted" style={{ fontSize: 11, marginTop: 3 }}>Sur la location vendue (hors frais &amp; caution)</div>
                  </div>
                  <div className="statcard" style={{ borderLeft: '3px solid #0891b2' }}>
                    <div className="muted" style={{ fontSize: 12, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '.5px' }}>Frais de service</div>
                    <div style={{ fontSize: 22, fontWeight: 800, marginTop: 6, color: '#0891b2' }}>{KD.fmtDA(finance?.serviceFeeTotal || 0)}</div>
                    <div className="muted" style={{ fontSize: 11, marginTop: 3 }}>Collectés des clients, reversés à Uleez</div>
                  </div>
                  <div className="statcard" style={{ borderLeft: '3px solid #be123c' }}>
                    <div className="muted" style={{ fontSize: 12, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '.5px' }}>Remises accordées</div>
                    <div style={{ fontSize: 22, fontWeight: 800, marginTop: 6, color: '#be123c' }}>- {KD.fmtDA(finance?.discountTotal || 0)}</div>
                    <div className="muted" style={{ fontSize: 11, marginTop: 3 }}>Codes promo, financés par Uleez</div>
                  </div>
                  <div className="statcard" style={{ borderLeft: '3px solid #0A4733' }}>
                    <div className="muted" style={{ fontSize: 12, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '.5px' }}>Revenu Uleez</div>
                    <div style={{ fontSize: 22, fontWeight: 800, marginTop: 6, color: '#0A4733' }}>{KD.fmtDA(finance?.keryaRevenue || 0)}</div>
                    <div className="muted" style={{ fontSize: 11, marginTop: 3 }}>Commission + frais de service − remises</div>
                  </div>
                  <div className="statcard" style={{ borderLeft: '3px solid #7c3aed' }}>
                    <div className="muted" style={{ fontSize: 12, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '.5px' }}>Net loueurs</div>
                    <div style={{ fontSize: 22, fontWeight: 800, marginTop: 6, color: '#7c3aed' }}>{KD.fmtDA(finance?.netTotal || 0)}</div>
                    <div className="muted" style={{ fontSize: 11, marginTop: 3 }}>Reversé aux prestataires</div>
                  </div>
                </div>
                {React.createElement(AdminFinanceChart, { grossSeries: revSeries, commissionRate, height: 250 })}
              </>}
        </div>
      </Card>

      {/* Map card — agencies by wilaya */}
      <div className="grid-2" style={{ gridTemplateColumns: '1fr 1.3fr', marginBottom: 20 }}>
        <Card>
          <CardHead title="Carte des agences" sub="Par chiffre d'affaires" icon={{ name: 'pin', color: 'green' }}
            right={!loading && mapData.length > 0 ? React.createElement(Badge, { cls: 'green' }, `${mapData.reduce((s,d)=>s+d.lessorCount,0)} loueurs`) : null} />
          <div className="card-pad">
            {loading
              ? React.createElement(Sk, { w: '100%', h: 420, r: 10 })
              : mapData.length === 0
                ? React.createElement(EmptyState, { variant: 'empty', icon: 'pin', title: 'Aucune agence approuvée', desc: 'La carte s\'affichera dès que des loueurs seront validés.' })
                : React.createElement(WilayaMap, { data: mapData, height: 420 })}
          </div>
        </Card>
        <Card>
          <CardHead title="Classement par wilaya" sub="Chiffre d'affaires et couverture" icon={{ name: 'trendUp', color: 'violet' }} />
          <div className="card-pad" style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
            {loading
              ? [0,1,2,3,4].map(i => React.createElement(Sk, { key: i, w: '100%', h: 20 }))
              : mapData.length === 0
                ? React.createElement(EmptyState, { variant: 'empty', icon: 'trendUp', title: 'Aucune donnée' })
                : (() => {
                    const top = mapData.slice(0, 6);
                    const maxRev = Math.max(...top.map(d => d.totalRevenue || 0), 1);
                    const colors = ['#059669','#2563eb','#7c3aed','#d97706','#e11d48','#0891b2'];
                    return top.map((d, i) => React.createElement('div', { key: d.wilaya },
                      React.createElement('div', { className: 'between', style: { marginBottom: 5 } },
                        React.createElement('span', { style: { fontWeight: 700, fontSize: 13.5 } }, d.wilaya),
                        React.createElement('span', { className: 'muted', style: { fontSize: 12, fontWeight: 600 } },
                          `${d.lessorCount} loueur${d.lessorCount > 1 ? 's' : ''} · ${d.totalRevenue >= 1000 ? (d.totalRevenue/1000).toFixed(0)+'k' : d.totalRevenue} DA`)),
                      React.createElement('div', { style: { height: 7, background: 'var(--slate-100)', borderRadius: 99 } },
                        React.createElement('div', { style: { width: `${Math.round((d.totalRevenue/maxRev)*100)}%`, height: '100%', background: colors[i % colors.length], borderRadius: 99, minWidth: d.lessorCount > 0 ? 8 : 0 } }))
                    ));
                  })()}
          </div>
        </Card>
      </div>

      <div className="grid-2" style={{ gridTemplateColumns: '1fr 1fr' }}>
        <Card>
          <CardHead title="Top wilayas" sub="Par nombre de réservations" icon={{ name: 'pin', color: 'green' }} />
          <div className="card-pad" style={{ display: 'flex', flexDirection: 'column', gap: 13 }}>
            {loading
              ? [0,1,2,3,4].map(i => <Sk key={i} w="100%" h={20} />)
              : !stats?.topWilayas?.length
                ? <EmptyState variant="empty" icon="pin" title="Aucune donnée" />
                : (() => {
                    const colors = ['#047857', '#2563eb', '#7c3aed', '#d97706', '#e11d48'];
                    const max = Math.max(...stats.topWilayas.map(w => w.count));
                    return stats.topWilayas.map((w, i) => (
                      <div key={i}>
                        <div className="between" style={{ marginBottom: 6 }}>
                          <span style={{ fontWeight: 700, fontSize: 13.5 }}>{w.wilaya}</span>
                          <span className="muted" style={{ fontSize: 13, fontWeight: 700 }}>{w.count}</span>
                        </div>
                        <div style={{ height: 8, background: 'var(--slate-100)', borderRadius: 99 }}>
                          <div style={{ width: `${Math.round((w.count / max) * 100)}%`, height: '100%', background: colors[i % colors.length], borderRadius: 99 }} />
                        </div>
                      </div>
                    ));
                  })()}
          </div>
        </Card>
        <Card>
          <CardHead title="Réservations récentes" icon={{ name: 'clipboard', color: 'blue' }}
            right={<Button size="sm" variant="ghost" iconRight="arrowUpRight" onClick={() => navigate('bookings')}>Tout voir</Button>} />
          <div className="card-pad" style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
            {loading
              ? [0,1,2,3].map(i => <div key={i} style={{ display: 'flex', gap: 12, padding: '10px 0' }}><Sk w={38} h={38} r={11} /><div style={{ flex: 1 }}><Sk w="60%" h={13} /><Sk w="40%" h={12} style={{ marginTop: 6 }} /></div></div>)
              : !stats?.recentBookings?.length
                ? <EmptyState variant="empty" icon="clipboard" title="Aucune réservation" />
                : stats.recentBookings.map((b, i) => (
                <div key={b.id} className="row" style={{ gap: 12, padding: '10px 0', borderBottom: i < stats.recentBookings.length - 1 ? '1px solid var(--line)' : 'none' }}>
                  <div className="sc-ico blue" style={{ width: 38, height: 38, borderRadius: 11 }}>
                    <Icon name="clipboard" size={18} />
                  </div>
                  <div style={{ flex: 1 }}>
                    <div style={{ fontWeight: 700, fontSize: 14 }}>{b.customerName} · {b.vehicleName}</div>
                    <div className="muted" style={{ fontSize: 13 }}>{KD.fmtDA(b.amount)}</div>
                  </div>
                  <StatusBadge map={KD.BOOKING_STATUS} value={b.status} />
                </div>
              ))}
          </div>
        </Card>
      </div>
    </div>
  );
}

// ============================================================
// ADMIN LESSORS (moderation)
// ============================================================
function AdminLessors({ navigate }) {
  const toast = useToast();
  const [rows, setRows] = useA1([]);
  const [loading, setLoading] = useA1(true);
  const [err, setErr] = useA1(false);
  const [tab, setTab] = useA1('all');
  const [reviewing, setReviewing] = useA1(null);
  const [reviewDocs, setReviewDocs] = useA1([]);
  const [docsLoading, setDocsLoading] = useA1(false);
  const [lessorReviews, setLessorReviews] = useA1([]);
  const [reviewsLoading, setReviewsLoading] = useA1(false);

  // Load the reviews left on this lessor's vehicles whenever a dossier opens (covers both the
  // row click and the "Voir le dossier" menu entry).
  useAE1(() => {
    if (!reviewing) { setLessorReviews([]); return; }
    let cancelled = false;
    setReviewsLoading(true);
    API.admin.getLessorReviews(reviewing.id)
      .then(d => { if (!cancelled) setLessorReviews(Array.isArray(d) ? d : []); })
      .catch(() => { if (!cancelled) setLessorReviews([]); })
      .finally(() => { if (!cancelled) setReviewsLoading(false); });
    return () => { cancelled = true; };
  }, [reviewing && reviewing.id]);
  const [rejecting, setRejecting] = useA1(null);
  // Ouvert quand le serveur refuse l'approbation faute d'abonnement en règle.
  const [forcing, setForcing] = useA1(null);
  const [suspending, setSuspending] = useA1(null);
  const [search, setSearch] = useA1('');
  const [actionLoading, setActionLoading] = useA1(null);
  const [creating, setCreating] = useA1(false);
  const [editing, setEditing] = useA1(null);
  const [deleting, setDeleting] = useA1(null);
  const [deleteLoading, setDeleteLoading] = useA1(false);
  const [commissionEditing, setCommissionEditing] = useA1(null);
  // Taux par défaut de la plateforme : nécessaire pour AFFICHER le taux effectif (le serveur, lui,
  // le résout tout seul à la lecture).
  const [defaultRate, setDefaultRate] = useA1(0.1);
  React.useEffect(() => {
    API.admin.getSettings().then((s) => { if (s && s.commissionRateDefault != null) setDefaultRate(Number(s.commissionRateDefault)); }).catch(() => {});
  }, []);
  const [resetPwd, setResetPwd] = useA1(null);
  const [invoiceType, setInvoiceType] = useA1('month');
  const [invoicePeriod, setInvoicePeriod] = useA1(() => {
    const now = new Date();
    return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
  });
  const [invoiceLoading, setInvoiceLoading] = useA1(null);

  const load = () => {
    setLoading(true);
    setErr(false);
    API.admin.getLessors()
      .then(data => {
        setRows(Array.isArray(data) ? data.map(API.normalizeLessor) : []);
        setLoading(false);
      })
      .catch(() => { setErr(true); setLoading(false); });
  };

  useAE1(() => { load(); }, []);

  const TABS = [
    { id: 'all',       label: 'Tous' },
    { id: 'pending',   label: 'En attente', alert: true },
    { id: 'approved',  label: 'Approuvés' },
    { id: 'suspended', label: 'Suspendus' },
    { id: 'rejected',  label: 'Refusés' },
  ];

  const filtered = rows
    .filter(l => tab === 'all' || l.status === tab)
    .filter(l => {
      if (!search) return true;
      const q = search.toLowerCase();
      return l.name.toLowerCase().includes(q)
        || l.email.toLowerCase().includes(q)
        || (l.phone || '').toLowerCase().includes(q)
        || (l.legalIdentifier || '').toLowerCase().includes(q)
        || (l.taxIdentifier || '').toLowerCase().includes(q);
    });

  const count = (id) => id === 'all' ? rows.length : rows.filter(l => l.status === id).length;

  // Un loueur n'existe sur Uleez qu'après avoir payé son abonnement. Le serveur refuse
  // l'approbation (409 SUBSCRIPTION_REQUIRED) tant que ce n'est pas le cas ; on ouvre alors
  // une confirmation où l'admin peut passer outre EN JUSTIFIANT — c'est ce motif qui part à
  // l'audit, et c'est ce qui distingue une dérogation assumée d'un contournement silencieux.
  const approve = async (l, opts) => {
    setActionLoading(l.id);
    try {
      const res = await API.admin.approveLessor(l.id, opts);
      setRows(rs => rs.map(x => x.id === l.id ? { ...x, status: 'approved' } : x));
      toast(res?.forced
        ? { variant: 'info', title: 'Loueur approuvé (dérogation)', desc: `${l.name} — sans abonnement en règle` }
        : { variant: 'success', title: 'Loueur approuvé', desc: l.name });
      setReviewing(null);
      setForcing(null);
    } catch (ex) {
      if (/SUBSCRIPTION_REQUIRED/.test(ex.message || '')) {
        setForcing({ lessor: l, message: ex.message });
      } else {
        toast({ variant: 'error', title: 'Erreur', desc: ex.message });
      }
    } finally {
      setActionLoading(null);
    }
  };

  const doReject = async (reason) => {
    setActionLoading(rejecting.id);
    try {
      await API.admin.rejectLessor(rejecting.id, reason);
      setRows(rs => rs.map(x => x.id === rejecting.id ? { ...x, status: 'rejected' } : x));
      toast({ variant: 'error', title: 'Demande refusée', desc: rejecting.name });
      setRejecting(null);
      setReviewing(null);
    } catch (ex) {
      toast({ variant: 'error', title: 'Erreur', desc: ex.message });
    } finally {
      setActionLoading(null);
    }
  };

  // Suspendre coupe désormais RÉELLEMENT l'activité (flotte hors recherche, messagerie
  // fermée, plus de confirmation de réservation). Un clic direct sans confirmation ni motif
  // n'est plus proportionné : on passe par une modale qui annonce l'impact.
  const suspend = async (reason) => {
    const l = suspending;
    setActionLoading(l.id);
    try {
      const res = await API.admin.suspendLessor(l.id, reason);
      setRows(rs => rs.map(x => x.id === l.id ? { ...x, status: 'suspended' } : x));
      const inFlight = res?.inFlight;
      toast({
        variant: 'info', title: 'Loueur suspendu',
        desc: inFlight && (inFlight.confirmed || inFlight.pending)
          ? `${l.name} — ${inFlight.confirmed} réservation(s) confirmée(s) restent honorées`
          : l.name,
      });
    } catch (ex) {
      toast({ variant: 'error', title: 'Erreur', desc: ex.message });
    } finally {
      setActionLoading(null);
      setSuspending(null);
    }
  };

  const doDelete = async () => {
    setDeleteLoading(true);
    try {
      await API.admin.deleteLessor(deleting.id);
      setRows(rs => rs.filter(x => x.id !== deleting.id));
      toast({ variant: 'info', title: 'Loueur supprimé', desc: deleting.name });
      setDeleting(null);
      setReviewing(null);
    } catch (ex) {
      toast({ variant: 'error', title: 'Erreur', desc: ex.message });
    } finally {
      setDeleteLoading(false);
    }
  };

  const reactivate = async (l) => {
    setActionLoading(l.id);
    try {
      await API.admin.reactivateLessor(l.id);
      setRows(rs => rs.map(x => x.id === l.id ? { ...x, status: 'approved' } : x));
      toast({ variant: 'success', title: 'Loueur réactivé', desc: l.name });
    } catch (ex) {
      toast({ variant: 'error', title: 'Erreur', desc: ex.message });
    } finally {
      setActionLoading(null);
    }
  };

  return (
    <div className="page-wide">
      <div className="toolbar" style={{ marginBottom: 14 }}>
        <div className="chips">
          {TABS.map(t => (
            <Chip key={t.id} active={tab === t.id} count={count(t.id)} alert={t.alert && count(t.id) > 0} onClick={() => setTab(t.id)}>
              {t.label}
            </Chip>
          ))}
        </div>
        <div className="spacer" />
        <div className="header-search" style={{ display: 'flex', width: 260, background: 'var(--surface)', border: '1px solid var(--line)' }}>
          <Icon name="search" size={17} />
          <input placeholder="Nom, e-mail, téléphone, RC/NIF…" value={search} onChange={e => setSearch(e.target.value)} />
        </div>
        <Button variant="secondary" icon="refresh" size="sm" onClick={load}>Actualiser</Button>
        <Button variant="primary" icon="plus" size="sm" onClick={() => setCreating(true)}>Nouveau loueur</Button>
      </div>

      <Card>
        {loading && <div className="card-pad"><TableSkeleton cols={7} /></div>}
        {!loading && err && <EmptyState variant="err" title="Impossible de charger les loueurs" action={<Button variant="secondary" icon="refresh" onClick={load}>Réessayer</Button>} />}
        {!loading && !err && (
          <div className="tbl-wrap">
            <table className="tbl">
              <thead>
                <tr>
                  <th>Loueur</th><th>Type</th><th>Wilaya</th><th>Inscription</th>
                  <th>Flotte</th><th>Note</th><th>Commission</th><th>Statut</th>
                  <th style={{ textAlign: 'right' }}>Actions</th>
                </tr>
              </thead>
              <tbody>
                {filtered.length === 0 && (
                  <tr><td colSpan={9} style={{ textAlign: 'center', padding: 32, color: 'var(--muted)' }}>Aucun loueur trouvé</td></tr>
                )}
                {filtered.map(l => (
                  <tr key={l.id} className="clickable" onClick={() => { setReviewing(l); setReviewDocs([]); setDocsLoading(true); API.admin.getLessorDocuments(l.id).then(d => { setReviewDocs(Array.isArray(d) ? d : []); }).catch(()=>{}).finally(()=>setDocsLoading(false)); }}>
                    <td>
                      <div className="celluser">
                        <Avatar name={l.name} src={l.logoUrl || undefined} size={36} square />
                        <div className="cu-meta"><div className="n">{l.name}</div><div className="e">{l.email}</div></div>
                      </div>
                    </td>
                    <td>{l.type}</td>
                    <td className="nowrap"><span className="row" style={{ gap: 6 }}><Icon name="pin" size={14} className="muted" />{l.wilaya}</span></td>
                    <td className="nowrap">{l.date}</td>
                    <td>{l.fleet > 0 ? `${l.fleet} véh.` : '—'}</td>
                    <td>{Number(l.rating) > 0 ? <span className="row" style={{ gap: 5 }}><Icon name="star" size={13} style={{ color: 'var(--amber-500)' }} />{Number(l.rating).toFixed(1)}</span> : '—'}</td>
                    <td>
                      {(() => {
                        // Taux EFFECTIF d'aujourd'hui, pas la valeur brute de la colonne : un « 0,0 % »
                        // affiché pour un surclassement oublié faisait croire que tout allait bien.
                        const e = effectiveCommission(l, defaultRate);
                        return (<>
                          {fmtPct(e.rate)}
                          <div className="muted" style={{ fontSize: 11 }}>
                            {e.welcomeActive ? `Bienvenue jusqu'au ${e.welcomeEnds.toLocaleDateString('fr-FR')}`
                              : e.stuckAtZero ? '⚠ surclassement à 0 %'
                              : e.hasOverride ? 'surclassement' : 'défaut'}
                          </div>
                        </>);
                      })()}
                    </td>
                    <td><StatusBadge map={KD.LESSOR_STATUS} value={l.status} /></td>
                    <td onClick={(e) => e.stopPropagation()} style={{ textAlign: 'right' }}>
                      {l.status === 'pending'
                        ? <div className="row" style={{ gap: 7, justifyContent: 'flex-end' }}>
                            <Button size="sm" variant="danger" icon="x" loading={actionLoading === l.id} onClick={() => setRejecting(l)}>Refuser</Button>
                            <Button size="sm" variant="success" icon="check" loading={actionLoading === l.id} onClick={() => approve(l)}>Approuver</Button>
                          </div>
                        : <Dropdown align="right"
                            trigger={<button className="btn btn-secondary btn-icon sm"><Icon name="moreV" size={16} /></button>}
                            items={[
                              { icon: 'eye', label: 'Voir le dossier', onClick: () => setReviewing(l) },
                              { icon: 'edit', label: 'Modifier', onClick: () => setEditing(l) },
                              { icon: 'key', label: 'Réinitialiser MDP', onClick: () => setResetPwd(l) },
                              { icon: 'card', label: 'Commission', onClick: () => setCommissionEditing(l) },
                              { icon: 'car', label: 'Voir les véhicules', onClick: () => navigate('vehicles') },
                              { sep: true },
                              l.status === 'approved'
                                ? { icon: 'ban', label: 'Suspendre', danger: true, onClick: () => setSuspending(l) }
                                : { icon: 'check', label: 'Réactiver', onClick: () => reactivate(l) },
                              { icon: 'trash', label: 'Supprimer', danger: true, onClick: () => setDeleting(l) },
                            ]} />}
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        )}
      </Card>

      {reviewing && (
        <Modal wide title={reviewing.name} desc={`${reviewing.type} · ${reviewing.wilaya}`} icon={{ name: 'building', color: 'blue' }} onClose={() => setReviewing(null)}
          footer={reviewing.status === 'pending' ? <>
            <Button variant="danger" icon="x" loading={actionLoading === reviewing.id} onClick={() => setRejecting(reviewing)}>Refuser</Button>
            <Button variant="primary" icon="check" loading={actionLoading === reviewing.id} onClick={() => approve(reviewing)}>Approuver le loueur</Button>
          </> : <>
            <Button variant="danger" icon="trash" onClick={() => setDeleting(reviewing)}>Supprimer</Button>
            <Dropdown align="right"
              trigger={<Button variant="secondary" icon="download" iconRight="chevDown">Réservations</Button>}
              items={[
                { label: 'Télécharger PDF', icon: 'clipboard', onClick: () => API.admin.exportLessorBookings(reviewing.id, null, 'pdf').catch(() => toast({ variant: 'error', title: 'Échec de l\'export' })) },
                { label: 'Télécharger Excel', icon: 'trendUp', onClick: () => API.admin.exportLessorBookings(reviewing.id, null, 'xlsx').catch(() => toast({ variant: 'error', title: 'Échec de l\'export' })) },
              ]} />
            {/* La période était figée sur « ce mois » : un loueur sans encaissement CE mois-ci
                exportait un PDF à 0 DZD, sans le moindre moyen de regarder une autre période.
                Le choix explicite est le seul correctif possible côté client. */}
            <Dropdown align="right"
              trigger={<Button variant="secondary" icon="download" iconRight="chevDown">Paiements</Button>}
              items={(() => {
                const fail = () => toast({ variant: 'error', title: 'Échec de l\'export' });
                const exp = (period, gran, fmt) =>
                  API.admin.exportLessorPayments(reviewing.id, period, gran, fmt).catch(fail);
                return [
                  { label: 'Ce mois — PDF', icon: 'clipboard', onClick: () => exp('month', 'day', 'pdf') },
                  { label: 'Ce trimestre — PDF', icon: 'clipboard', onClick: () => exp('quarter', 'week', 'pdf') },
                  { label: 'Cette année — PDF', icon: 'clipboard', onClick: () => exp('year', 'month', 'pdf') },
                  { label: '12 derniers mois — PDF', icon: 'clipboard', onClick: () => exp('last12', 'month', 'pdf') },
                  { sep: true },
                  { label: 'Cette année — Excel', icon: 'trendUp', onClick: () => exp('year', 'month', 'xlsx') },
                  { label: '12 derniers mois — Excel', icon: 'trendUp', onClick: () => exp('last12', 'month', 'xlsx') },
                ];
              })()} />
            <Button variant="secondary" icon="edit" onClick={() => setEditing(reviewing)}>Modifier</Button>
            <Button variant="secondary" onClick={() => setReviewing(null)}>Fermer</Button>
          </>}>
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 18 }}>
            <div>
              <div className="kv"><span className="k">Email</span><span className="v">{reviewing.email}</span></div>
              <div className="kv"><span className="k">Téléphone</span><span className="v">{reviewing.phone || '—'}</span></div>
              <div className="kv"><span className="k">Wilaya</span><span className="v">{reviewing.wilaya}</span></div>
              <div className="kv"><span className="k">Inscription</span><span className="v">{reviewing.date}</span></div>
              <div className="kv"><span className="k">Statut</span><span className="v"><StatusBadge map={KD.LESSOR_STATUS} value={reviewing.status} /></span></div>
            </div>
            <div>
              <div className="flabel" style={{ marginBottom: 8 }}>Documents</div>
              {docsLoading && <Sk w="100%" h={60} r={8} />}
              {!docsLoading && reviewDocs.length === 0 && <div className="muted" style={{ fontSize: 13 }}>Aucun document déposé pour l'instant.</div>}
              {!docsLoading && reviewDocs.map((doc, i) => {
                const DOC_LABELS = { rc: 'Registre de commerce', cin: 'Carte d\'identité (CIN)', rib: 'RIB / Relevé bancaire', driving_license: 'Permis de conduire', passport: 'Passeport' };
                const label = DOC_LABELS[doc.documentType] || doc.documentType;
                // The mime type now comes from the row (the storage ref carries no
                // extension — the file is encrypted, and nothing about it is guessable).
                const isImg = (doc.mimeType || '').startsWith('image/');
                // `hasContent` is false for legacy rows whose plaintext file the v0.7.4
                // migration deleted: there is nothing to show until the lessor re-uploads.
                return React.createElement('div', { key: doc.id || i, className: 'req', style: { padding: 11, marginBottom: 8 } },
                  // A row with no file was flipped to 'rejected' by the v0.7.4 migration, but
                  // nobody rejected it — treat it as pending so it does not read as a failure.
                  React.createElement('div', { className: `sc-ico ${!doc.hasContent ? 'blue' : doc.verificationStatus === 'approved' ? 'green' : doc.verificationStatus === 'rejected' ? 'rose' : 'blue'}`, style: { width: 34, height: 34, borderRadius: 9 } },
                    React.createElement(Icon, { name: isImg ? 'image' : 'clipboard', size: 16 })),
                  React.createElement('div', { className: 'req-body' },
                    React.createElement('div', { style: { fontWeight: 700, fontSize: 13.5 } }, label),
                    React.createElement('div', { className: 'muted', style: { fontSize: 12 } },
                      doc.hasContent ? (isImg ? 'Image chiffrée' : 'Document chiffré') : 'À renvoyer',
                      ' · ',
                      !doc.hasContent ? 'En attente'
                        : doc.verificationStatus === 'approved' ? '✓ Vérifié'
                          : doc.verificationStatus === 'rejected' ? '✗ Rejeté' : 'En attente')),
                  doc.hasContent && React.createElement(IconButton, {
                    name: 'download', size: 'sm', title: 'Télécharger le document (déchiffré)',
                    onClick: () => API.docs.download(doc, `${doc.documentType}`)
                      .catch(() => toast({ variant: 'error', title: 'Téléchargement impossible' })),
                  }));
              })}
            </div>
          </div>

          {reviewing.status !== 'pending' && (
            <div style={{ marginTop: 20, paddingTop: 16, borderTop: '1px solid var(--line)' }}>
              <div className="flabel" style={{ marginBottom: 8 }}>Facture de commission</div>
              <div style={{ display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' }}>
                <Select value={invoiceType} onChange={e => setInvoiceType(e.target.value)} style={{ width: 130 }}>
                  <option value="month">Mensuelle</option>
                  <option value="quarter">Trimestrielle</option>
                </Select>
                <Input
                  value={invoicePeriod}
                  onChange={e => setInvoicePeriod(e.target.value)}
                  placeholder={invoiceType === 'quarter' ? 'AAAA-Q1' : 'AAAA-MM'}
                  style={{ width: 120 }}
                />
                <Button
                  variant="secondary" icon="download"
                  loading={invoiceLoading === 'download'}
                  onClick={() => {
                    setInvoiceLoading('download');
                    API.admin.getLessorInvoice(reviewing.id, invoicePeriod, invoiceType)
                      .catch(() => toast({ variant: 'error', title: 'Échec de la génération de la facture' }))
                      .finally(() => setInvoiceLoading(null));
                  }}
                >Télécharger</Button>
                <Button
                  variant="primary" icon="send"
                  loading={invoiceLoading === 'send'}
                  onClick={() => {
                    setInvoiceLoading('send');
                    API.admin.sendLessorInvoice(reviewing.id, invoicePeriod, invoiceType)
                      .then(() => toast({ variant: 'success', title: 'Facture envoyée par e-mail' }))
                      .catch((ex) => toast({ variant: 'error', title: 'Échec de l\'envoi', desc: ex.message }))
                      .finally(() => setInvoiceLoading(null));
                  }}
                >Envoyer par e-mail</Button>
              </div>
            </div>
          )}

          <div style={{ marginTop: 20, paddingTop: 16, borderTop: '1px solid var(--line)' }}>
            <div className="flabel" style={{ marginBottom: 8, display: 'flex', alignItems: 'center', gap: 8 }}>
              <span>Avis reçus sur les véhicules</span>
              {Number(reviewing.rating) > 0 && (
                <span className="row" style={{ gap: 4, color: 'var(--amber-600)', fontWeight: 700 }}>
                  <Icon name="star" size={13} style={{ color: 'var(--amber-500)' }} />{Number(reviewing.rating).toFixed(1)}
                  <span className="muted" style={{ fontWeight: 500 }}>· {lessorReviews.length} avis</span>
                </span>
              )}
            </div>
            {reviewsLoading && <Sk w="100%" h={60} r={8} />}
            {!reviewsLoading && lessorReviews.length === 0 && (
              <div className="muted" style={{ fontSize: 13 }}>Aucun avis pour l'instant.</div>
            )}
            {!reviewsLoading && lessorReviews.map((rv, i) => (
              <div key={rv.id || i} className="req" style={{ padding: 11, marginBottom: 8, alignItems: 'flex-start' }}>
                <div className="sc-ico amber" style={{ width: 34, height: 34, borderRadius: 9 }}>
                  <Icon name="star" size={16} />
                </div>
                <div className="req-body">
                  <div className="row" style={{ gap: 8, justifyContent: 'space-between' }}>
                    <div style={{ fontWeight: 700, fontSize: 13.5 }}>{rv.customerName || 'Anonyme'}</div>
                    <div className="row" style={{ gap: 3, color: 'var(--amber-600)', fontWeight: 700, fontSize: 12.5 }}>
                      <Icon name="star" size={12} style={{ color: 'var(--amber-500)' }} />{Number(rv.rating).toFixed(1)}
                    </div>
                  </div>
                  {rv.comment && <div style={{ fontSize: 13, marginTop: 3 }}>{rv.comment}</div>}
                  <div className="muted" style={{ fontSize: 11.5, marginTop: 3 }}>
                    {rv.createdAt ? new Date(rv.createdAt).toLocaleDateString('fr-FR', { day: 'numeric', month: 'short', year: 'numeric' }) : ''}
                  </div>
                </div>
              </div>
            ))}
          </div>
        </Modal>
      )}

      {rejecting && <RejectModal title="Refuser le loueur" who={rejecting.name} onClose={() => setRejecting(null)} onConfirm={doReject} />}
      {creating && <LessorFormModal onClose={() => setCreating(false)} onSaved={(newLessor) => { setRows(rs => [API.normalizeLessor(newLessor), ...rs]); setCreating(false); }} />}
      {editing && <LessorFormModal lessor={editing} onClose={() => setEditing(null)} onSaved={(updated) => { const norm = API.normalizeLessor(updated); setRows(rs => rs.map(x => x.id === norm.id ? norm : x)); setEditing(null); setReviewing(null); }} />}
      {deleting && <DeleteLessorModal lessor={deleting} loading={deleteLoading} onClose={() => setDeleting(null)} onConfirm={doDelete} />}
      {commissionEditing && <CommissionModal lessor={commissionEditing} defaultRate={defaultRate} onClose={() => setCommissionEditing(null)}
        onSaved={(updated) => { const norm = API.normalizeLessor(updated); setRows(rs => rs.map(x => x.id === norm.id ? norm : x)); setCommissionEditing(null); }} />}
      {resetPwd && <PasswordResetModal lessor={resetPwd} onClose={() => setResetPwd(null)} />}
      {suspending && <SuspendLessorModal lessor={suspending} loading={actionLoading === suspending.id}
        onClose={() => setSuspending(null)} onConfirm={suspend} />}
      {forcing && <ForceApproveModal info={forcing} loading={actionLoading === forcing.lessor.id}
        onClose={() => setForcing(null)}
        onConfirm={(reason) => approve(forcing.lessor, { force: true, reason })} />}
    </div>
  );
}

// ─── Modal : Suspension d'un loueur ─────────────────────────────────────────
// La suspension a maintenant des conséquences réelles et immédiates. Les énoncer avant de
// confirmer évite qu'un admin découvre après coup que la messagerie du loueur est coupée.
function SuspendLessorModal({ lessor, loading, onClose, onConfirm }) {
  const [reason, setReason] = useA1('');
  return (
    <Modal title="Suspendre ce loueur" desc={lessor.name} icon={{ name: 'ban', color: 'rose' }} onClose={onClose}
      footer={<>
        <Button variant="ghost" onClick={onClose}>Annuler</Button>
        <Button variant="danger" icon="ban" loading={loading} onClick={() => onConfirm(reason.trim() || undefined)}>
          Suspendre
        </Button>
      </>}>
      <div className="banner-warn" style={{ marginBottom: 12 }}>
        <strong>Effet immédiat :</strong>
        <ul style={{ margin: '6px 0 0', paddingLeft: 18, lineHeight: 1.7 }}>
          <li>toute sa flotte quitte la recherche et n'est plus réservable ;</li>
          <li>il ne peut plus écrire à ses locataires ni recevoir leurs messages ;</li>
          <li>il ne peut plus confirmer de nouvelle réservation.</li>
        </ul>
      </div>
      <div className="banner-info" style={{ marginBottom: 12 }}>
        Ses réservations <strong>déjà confirmées restent honorées</strong>, et il garde l'accès
        à l'assistance Uleez pour régulariser. Les commissions dues continuent d'être facturées.
      </div>
      <Field label="Motif (facultatif, conservé dans l'audit)">
        <Textarea placeholder="Abonnement impayé, litige client, document expiré…"
          value={reason} onChange={(e) => setReason(e.target.value)} />
      </Field>
    </Modal>
  );
}

// ─── Modal : Approbation forcée (sans abonnement en règle) ──────────────────
function ForceApproveModal({ info, loading, onClose, onConfirm }) {
  const [reason, setReason] = useA1('');
  const [touched, setTouched] = useA1(false);
  const err = touched && reason.trim().length < 5 ? 'Un motif d\'au moins 5 caractères est obligatoire.' : '';
  return (
    <Modal title="Abonnement non réglé" desc={info.lessor.name}
      icon={{ name: 'alert', color: 'amber' }} onClose={onClose}
      footer={<>
        <Button variant="ghost" onClick={onClose}>Annuler</Button>
        <Button variant="primary" icon="check" loading={loading}
          onClick={() => { setTouched(true); if (reason.trim().length >= 5) onConfirm(reason.trim()); }}>
          Approuver quand même
        </Button>
      </>}>
      <div className="banner-warn" style={{ marginBottom: 12 }}>{info.message}</div>
      <div className="muted" style={{ fontSize: 12.5, marginBottom: 12, lineHeight: 1.6 }}>
        La marche normale est d'ouvrir son abonnement depuis <strong>Facturation → Abonnements</strong>,
        puis d'enregistrer son règlement : l'approbation se débloque alors d'elle-même.
        Ne forcez que pour un paiement reçu hors plateforme ou un geste commercial.
      </div>
      <Field label="Motif de la dérogation" error={err}>
        <Textarea placeholder="Paiement en espèces reçu le…, accord commercial…"
          value={reason} error={!!err} onChange={(e) => setReason(e.target.value)} />
      </Field>
    </Modal>
  );
}

// ─── Modal : Réinitialisation mot de passe loueur ───────────────────────────
function PasswordResetModal({ lessor, onClose }) {
  const toast = useToast();
  const [pwd, setPwd] = useA1('');
  const [loading, setLoading] = useA1(false);

  const generatePwd = () => {
    const n = Math.floor(1000 + Math.random() * 9000);
    setPwd(`Uleez@${n}`);
  };

  const submit = async () => {
    if (!pwd.trim()) return;
    const pwdRegex = /^(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,}$/;
    if (!pwdRegex.test(pwd)) {
      toast({ variant: 'error', title: 'Mot de passe invalide', desc: 'Min. 8 caractères, 1 majuscule, 1 chiffre, 1 caractère spécial' });
      return;
    }
    setLoading(true);
    try {
      await API.admin.updateLessor(lessor.id, { password: pwd });
      toast({ variant: 'success', title: 'Mot de passe réinitialisé', desc: `Communiquez ce mot de passe au loueur : ${pwd}` });
      onClose();
    } catch (ex) {
      toast({ variant: 'error', title: 'Erreur', desc: ex.message });
    } finally {
      setLoading(false);
    }
  };

  return (
    <Modal title="Réinitialiser le mot de passe" desc={lessor.name} icon={{ name: 'key', color: 'amber' }} onClose={onClose}
      footer={<>
        <Button variant="secondary" onClick={onClose}>Annuler</Button>
        <Button variant="primary" icon="key" loading={loading} onClick={submit}>Réinitialiser</Button>
      </>}>
      <div style={{ marginBottom: 16 }}>
        <div className="flabel" style={{ marginBottom: 6 }}>Nouveau mot de passe</div>
        <div style={{ display: 'flex', gap: 8 }}>
          <input className="input" style={{ flex: 1, fontFamily: 'monospace' }} value={pwd} onChange={e => setPwd(e.target.value)} placeholder="Min. 8 car., 1 maj., 1 chiffre, 1 spécial" />
          <Button variant="secondary" icon="refresh" onClick={generatePwd}>Générer</Button>
        </div>
        <div className="muted" style={{ fontSize: 12, marginTop: 6 }}>
          Le mot de passe généré respecte les exigences de sécurité. Communiquez-le au loueur après réinitialisation.
        </div>
      </div>
    </Modal>
  );
}

// ─── Modal : Commission & période de bienvenue ───────────────────────────────
// Taux qui s'applique AUJOURD'HUI à un loueur, et d'où il vient. Même ordre que le serveur
// (`resolveCommissionRate`) : bienvenue en cours (0 %) > surclassement > défaut plateforme.
// Trois états à ne jamais confondre : la bienvenue est TEMPORAIRE et s'éteint seule ; le
// surclassement est PERMANENT et bat le défaut pour toujours.
function effectiveCommission(l, defaultRate) {
  const now = new Date();
  const welcomeEnds = l.welcomePeriodEndsAt ? new Date(l.welcomePeriodEndsAt) : null;
  const welcomeActive = !!welcomeEnds && welcomeEnds > now;
  const welcomeEnded = !!welcomeEnds && welcomeEnds <= now;
  const hasOverride = l.commissionRate != null;
  const rate = welcomeActive ? 0 : hasOverride ? Number(l.commissionRate) : defaultRate;
  const source = welcomeActive ? 'welcome' : hasOverride ? 'override' : 'default';
  // Le piège historique : un surclassement à 0 % resté actif alors que la bienvenue est finie.
  const stuckAtZero = hasOverride && Number(l.commissionRate) === 0 && !welcomeActive;
  return { rate, source, welcomeActive, welcomeEnded, welcomeEnds, hasOverride, stuckAtZero };
}
const fmtPct = (r) => (r == null || Number.isNaN(r) ? '—' : `${(Math.round(r * 1000) / 10).toString().replace('.', ',')} %`);

function CommissionModal({ lessor, defaultRate, onClose, onSaved }) {
  const toast = useToast();
  const [rate, setRate] = useA1(lessor.commissionRate != null ? String(Number(lessor.commissionRate) * 100) : '');
  const [welcomeUntil, setWelcomeUntil] = useA1(lessor.welcomePeriodEndsAt ? lessor.welcomePeriodEndsAt.slice(0, 10) : '');
  const [loading, setLoading] = useA1(false);
  const eff = effectiveCommission(lessor, defaultRate);
  const rateNum = rate.trim() === '' ? null : Number(rate);
  // 0 % en surclassement est refusé (le serveur aussi) : il battait le défaut pour toujours, période
  // de bienvenue ou non, ce qui rendait la commission « bloquée à 0 % ».
  const rateError = rateNum !== null && (Number.isNaN(rateNum) || rateNum <= 0 || rateNum > 100)
    ? (rateNum === 0
        ? "0 % ne peut pas être un taux permanent. Pour offrir 0 % pendant un temps, utilisez la période de bienvenue ci-dessus."
        : 'Taux invalide (entre 0,1 et 100).')
    : null;

  const submit = async () => {
    if (rateError) return;
    setLoading(true);
    try {
      const payload = {
        commissionRate: rateNum === null ? null : rateNum / 100,
        // include the entire selected day (local time) in the welcome period
        welcomePeriodEndsAt: welcomeUntil ? new Date(`${welcomeUntil}T23:59:59`).toISOString() : null,
      };
      const updated = await API.admin.updateLessor(lessor.id, payload);
      toast({ variant: 'success', title: 'Commission mise à jour', desc: lessor.name });
      onSaved(updated);
    } catch (ex) {
      toast({ variant: 'error', title: 'Erreur', desc: ex.message });
    } finally {
      setLoading(false);
    }
  };

  const SOURCE = { welcome: 'période de bienvenue en cours', override: 'surclassement permanent', default: 'taux par défaut de la plateforme' };
  return (
    <Modal title="Commission & période de bienvenue" desc={lessor.name} icon={{ name: 'card', color: 'blue' }} onClose={onClose}
      footer={<>
        <Button variant="secondary" onClick={onClose}>Annuler</Button>
        <Button variant="primary" loading={loading} disabled={!!rateError} onClick={submit}>Enregistrer</Button>
      </>}>
      <div>
        <div className="card card-pad" style={{ marginBottom: 16, background: 'var(--slate-50, var(--surface))' }}>
          <div className="muted" style={{ fontSize: 12 }}>Taux appliqué aujourd'hui</div>
          <div style={{ fontWeight: 800, fontSize: 22 }}>{fmtPct(eff.rate)}</div>
          <div className="muted" style={{ fontSize: 12.5 }}>
            {SOURCE[eff.source]}
            {eff.welcomeActive && eff.welcomeEnds ? ` — jusqu'au ${eff.welcomeEnds.toLocaleDateString('fr-FR')}, puis retour à ${fmtPct(eff.hasOverride ? Number(lessor.commissionRate) : defaultRate)}` : ''}
          </div>
          {eff.welcomeEnded && !eff.stuckAtZero && (
            <div className="muted" style={{ fontSize: 12.5, marginTop: 6 }}>
              Période de bienvenue échue le {eff.welcomeEnds.toLocaleDateString('fr-FR')} : le taux est revenu automatiquement.
            </div>
          )}
          {eff.stuckAtZero && (
            <div style={{ fontSize: 12.5, marginTop: 6, color: 'var(--rose-600, #be123c)', fontWeight: 600 }}>
              ⚠ Un surclassement à 0 % est resté actif. Il sera effacé automatiquement cette nuit, ou videz le champ « Surclassement » et enregistrez.
            </div>
          )}
        </div>

        <div style={{ marginBottom: 20 }}>
          <label className="flabel">Période de bienvenue <span className="muted" style={{ fontWeight: 500 }}>— temporaire, 0 %</span></label>
          <input className="input" type="date" value={welcomeUntil} onChange={e => setWelcomeUntil(e.target.value)} />
          <div className="muted" style={{ fontSize: 12, marginTop: 4 }}>
            0 % jusqu'à cette date incluse, puis retour automatique au surclassement (s'il existe) ou au défaut. Laisser vide : aucune période.
          </div>
        </div>
        <div>
          <label className="flabel">Surclassement du taux (%) <span className="muted" style={{ fontWeight: 500 }}>— permanent</span></label>
          <input className="input" type="number" step="0.1" min="0.1" max="100" placeholder={`Défaut plateforme (${fmtPct(defaultRate)})`} value={rate} onChange={e => setRate(e.target.value)} />
          {rateError
            ? <div style={{ fontSize: 12, marginTop: 4, color: 'var(--rose-600, #be123c)' }}>{rateError}</div>
            : <div className="muted" style={{ fontSize: 12, marginTop: 4 }}>Remplace le défaut pour TOUJOURS. Laisser vide pour appliquer le taux par défaut de la plateforme.</div>}
        </div>
      </div>
    </Modal>
  );
}

// ─── Modal : Créer / Modifier un loueur ──────────────────────────────────────
// Single source of truth: KD.WILAYAS (data.jsx) — the 58 official wilayas.
const WILAYAS = KD.WILAYAS;

function LessorFormModal({ lessor, onClose, onSaved }) {
  const toast = useToast();
  const isEdit = !!lessor;
  const [form, setForm] = useA1({
    firstName: lessor?.owner?.firstName || lessor?.name?.split(' ')[0] || '',
    lastName: lessor?.owner?.lastName || lessor?.name?.split(' ').slice(1).join(' ') || '',
    email: lessor?.email || '',
    phone: lessor?.phone || '',
    password: '',
    businessName: lessor?.name || '',
    type: lessor?.type === 'Agence' ? 'agency' : 'independent',
    wilaya: lessor?.wilaya || 'Alger',
  });
  const [saving, setSaving] = useA1(false);
  const [errors, setErrors] = useA1({});
  const [logoUrl, setLogoUrl] = useA1(lessor?.logoUrl || null);
  const [logoUploading, setLogoUploading] = useA1(false);
  const logoInputRef = React.useRef(null);

  const set = (k) => (e) => setForm(f => ({ ...f, [k]: e.target.value }));

  const pickLogo = async (e) => {
    const file = e.target.files && e.target.files[0];
    e.target.value = '';
    if (!file || !lessor) return;
    setLogoUploading(true);
    try {
      const updated = await API.admin.uploadLessorLogo(lessor.id, file);
      const norm = API.normalizeLessor(updated);
      setLogoUrl(norm.logoUrl);
      onSaved(norm);
      toast({ variant: 'success', title: 'Photo mise à jour' });
    } catch (ex) {
      toast({ variant: 'error', title: 'Erreur', desc: ex.message });
    } finally {
      setLogoUploading(false);
    }
  };

  const validate = () => {
    const e = {};
    if (!form.firstName.trim()) e.firstName = 'Requis';
    if (!form.lastName.trim()) e.lastName = 'Requis';
    if (!form.email.trim() || !form.email.includes('@')) e.email = 'Email invalide';
    if (!form.phone.trim()) e.phone = 'Requis';
    if (!isEdit && (!form.password || form.password.length < 8)) e.password = 'Min. 8 caractères';
    if (isEdit && form.password && form.password.length < 8) e.password = 'Min. 8 caractères';
    if (!form.businessName.trim()) e.businessName = 'Requis';
    if (!form.wilaya.trim()) e.wilaya = 'Requis';
    return e;
  };

  const submit = async () => {
    const errs = validate();
    if (Object.keys(errs).length) { setErrors(errs); return; }
    setSaving(true);
    try {
      if (isEdit) {
        const payload = { ...form };
        if (!payload.password) delete payload.password;
        delete payload.phone; // jamais réécrit depuis le back-office (le serveur le refuserait aussi)
        const result = await API.admin.updateLessor(lessor.id, payload);
        toast({ variant: 'success', title: 'Loueur modifié', desc: `${form.businessName}` });
        onSaved(result);
      } else {
        const result = await API.admin.createLessor(form);
        toast({ variant: 'success', title: 'Loueur créé', desc: `${form.businessName} — ${form.email}` });
        onSaved(result.lessor || result);
      }
    } catch (ex) {
      toast({ variant: 'error', title: 'Erreur', desc: ex.message });
    } finally {
      setSaving(false);
    }
  };

  return (
    <Modal title={isEdit ? 'Modifier le loueur' : 'Nouveau loueur'} desc={isEdit ? 'Modifier les informations du compte loueur' : 'Créer un compte loueur directement approuvé'} icon={{ name: 'userPlus', color: 'blue' }}
      onClose={onClose}
      footer={<>
        <Button variant="secondary" onClick={onClose}>Annuler</Button>
        <Button variant="primary" icon="check" loading={saving} onClick={submit}>{isEdit ? 'Enregistrer' : 'Créer le compte'}</Button>
      </>}>
      {isEdit && (
        <div className="row" style={{ gap: 12, alignItems: 'center', marginBottom: 18 }}>
          <Avatar name={form.businessName} src={logoUrl || undefined} size={52} square />
          <div>
            <input ref={logoInputRef} type="file" accept="image/jpeg,image/png,image/webp" style={{ display: 'none' }} onChange={pickLogo} />
            <Button variant="secondary" size="sm" icon="image" loading={logoUploading} onClick={() => logoInputRef.current?.click()}>
              {logoUrl ? 'Changer la photo' : 'Ajouter une photo'}
            </Button>
            <div className="muted" style={{ fontSize: 11.5, marginTop: 4 }}>JPG, PNG ou WEBP — 5 Mo max</div>
          </div>
        </div>
      )}
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 }}>
        <div>
          <label className="flabel">Prénom *</label>
          <input className={`input${errors.firstName ? ' input-err' : ''}`} value={form.firstName} onChange={set('firstName')} placeholder="Mohamed" />
          {errors.firstName && <div className="ferr">{errors.firstName}</div>}
        </div>
        <div>
          <label className="flabel">Nom *</label>
          <input className={`input${errors.lastName ? ' input-err' : ''}`} value={form.lastName} onChange={set('lastName')} placeholder="Bensalem" />
          {errors.lastName && <div className="ferr">{errors.lastName}</div>}
        </div>
        <div>
          <label className="flabel">Email *</label>
          <input className={`input${errors.email ? ' input-err' : ''}`} type="email" value={form.email} onChange={set('email')} placeholder="agence@exemple.dz" />
          {errors.email && <div className="ferr">{errors.email}</div>}
        </div>
        <div>
          <label className="flabel">Téléphone *</label>
          <input className={`input${errors.phone ? ' input-err' : ''}`} value={form.phone} onChange={set('phone')} placeholder="+213 6XX XXX XXX" disabled={isEdit} />
          {isEdit && <div className="muted" style={{ fontSize: 12, marginTop: 4 }}>Le numéro sert de sel à la connexion du titulaire : il ne peut pas être modifié ici. Le titulaire le corrige depuis son application.</div>}
          {errors.phone && <div className="ferr">{errors.phone}</div>}
        </div>
        <div>
          <label className="flabel">Mot de passe {isEdit ? '' : '*'}</label>
          <input className={`input${errors.password ? ' input-err' : ''}`} type="password" value={form.password} onChange={set('password')} placeholder={isEdit ? 'Laisser vide pour ne pas changer' : 'Min. 8 caractères'} />
          {errors.password && <div className="ferr">{errors.password}</div>}
        </div>
        <div>
          <label className="flabel">Nom de l'agence / société *</label>
          <input className={`input${errors.businessName ? ' input-err' : ''}`} value={form.businessName} onChange={set('businessName')} placeholder="Alger Auto Rent" />
          {errors.businessName && <div className="ferr">{errors.businessName}</div>}
        </div>
        <div>
          <label className="flabel">Type</label>
          <select className="input" value={form.type} onChange={set('type')}>
            <option value="agency">Agence</option>
            <option value="independent">Particulier</option>
          </select>
        </div>
        <div>
          <label className="flabel">Wilaya *</label>
          <select className={`input${errors.wilaya ? ' input-err' : ''}`} value={form.wilaya} onChange={set('wilaya')}>
            {WILAYAS.map(w => <option key={w} value={w}>{w}</option>)}
          </select>
          {errors.wilaya && <div className="ferr">{errors.wilaya}</div>}
        </div>
      </div>
      {!isEdit && <div className="banner-info" style={{ marginTop: 14, padding: '10px 14px', borderRadius: 10, background: 'var(--blue-50)', border: '1px solid var(--blue-200)', fontSize: 13, color: 'var(--blue-700)' }}>
        Le loueur sera directement approuvé et pourra se connecter immédiatement.
      </div>}
    </Modal>
  );
}

// ─── Modal : Confirmer la suppression d'un loueur ────────────────────────────
function DeleteLessorModal({ lessor, onClose, onConfirm, loading }) {
  return (
    <Modal title="Supprimer le loueur" desc={lessor.name} icon={{ name: 'xCircle', color: 'rose' }} onClose={onClose}
      footer={<>
        <Button variant="ghost" onClick={onClose}>Annuler</Button>
        <Button variant="danger" icon="trash" loading={loading} onClick={onConfirm}>Supprimer définitivement</Button>
      </>}>
      <p>Cette action supprimera définitivement le compte de <strong>{lessor.name}</strong> ({lessor.email}) ainsi que son profil loueur. Cette action est irréversible.</p>
    </Modal>
  );
}

Object.assign(window, { AdminDashboard, AdminLessors, LessorFormModal, DeleteLessorModal });
