/* Uleez — Lessor screens part 1: dashboard, bookings list, booking detail */
const { useState: useS1, useEffect: useE1, useRef: useR1 } = React;

/** Raccourci de traduction, abonné aux changements de langue. */
const useT1 = () => (window.I18N ? I18N.useT() : ((k) => k));

/** Locale BCP-47 de la langue choisie par le loueur, pour `toLocaleDateString`.
 *
 *  Fonction au niveau module, et non constante dans un composant : la version précédente vivait
 *  dans `LessorDashboard` alors que `LessorBookings` la lisait aussi, d'où un
 *  `ReferenceError: l1Locale is not defined` — écran blanc — au rendu de la liste des
 *  réservations. Elle était de surcroît lue par le regroupement des départs à venir AVANT sa
 *  propre déclaration (zone morte temporelle), ce qui plantait aussi le tableau de bord dès
 *  qu'un départ tombait à plus de deux jours. Évaluée à l'appel, elle suit les changements de
 *  langue sans recalcul explicite. */
const l1Locale = () => {
  const lang = window.I18N ? I18N.lang : 'fr';
  return lang === 'ar' ? 'ar-DZ' : lang === 'en' ? 'en-GB' : 'fr-FR';
};

// ---------- shared small bits ----------
function StateSeg({ value, onChange }) {
  // Outil de PRÉVISUALISATION interne (bascule entre les états d'un écran pour les relire) —
  // il n'est pas destiné au loueur et reste donc en français.
  const opts = [['populated', 'Rempli'], ['loading', 'Chargement'], ['empty', 'Vide'], ['error', 'Erreur']];
  return (
    <div className="seg" title="Aperçu des états">
      {opts.map(([k, l]) => (
        <button key={k} className={value === k ? 'on' : ''} onClick={() => onChange(k)}>{l}</button>
      ))}
    </div>
  );
}

function RejectModal({ title, who, onClose, onConfirm }) {
  const t = useT1();
  const [reason, setReason] = useS1('');
  const [touched, setTouched] = useS1(false);
  const err = touched && !reason.trim() ? t('book.reasonRequired') : '';
  return (
    <Modal title={title} desc={who} icon={{ name: 'xCircle', color: 'rose' }} onClose={onClose}
      footer={<>
        <Button variant="ghost" onClick={onClose}>{t('common.cancel')}</Button>
        <Button variant="danger" icon="x" onClick={() => { setTouched(true); if (reason.trim()) onConfirm(reason); }}>{t('book.confirmReject')}</Button>
      </>}>
      <Field label={t('book.rejectReason')} error={err}>
        <Textarea placeholder={t('book.rejectReasonHint')} value={reason}
          error={!!err} onChange={(e) => setReason(e.target.value)} />
      </Field>
    </Modal>
  );
}

// ============================================================
// LESSOR DASHBOARD
// ============================================================
function LessorDashboard({ navigate, currentUser }) {
  const t = useT1();
  const toast = useToast();
  const [bookings, setBookings] = useS1([]);
  const [vehicles, setVehicles] = useS1([]);
  const [payments, setPayments] = useS1(null);
  const [lessorProfile, setLessorProfile] = useS1(null);
  const [loading, setLoading] = useS1(true);
  const [err, setErr] = useS1(false);
  const [rejecting, setRejecting] = useS1(null);
  const [actionLoading, setActionLoading] = useS1(null);

  const load = () => {
    setLoading(true);
    setErr(false);
    Promise.all([
      API.lessor.getBookings().catch(() => []),
      API.lessor.getVehicles().catch(() => []),
      API.lessor.getPayments('month').catch(() => null),
      API.lessor.getProfile().catch(() => null),
    ])
      .then(([bookingsData, vehiclesData, paymentsData, profileData]) => {
        setBookings(Array.isArray(bookingsData) ? bookingsData.map(API.normalizeBooking) : []);
        setVehicles(Array.isArray(vehiclesData) ? vehiclesData : []);
        setPayments(paymentsData);
        setLessorProfile(profileData);
        setLoading(false);
      })
      .catch(() => { setErr(true); setLoading(false); });
  };

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

  const pendingRequests = bookings.filter(b => b.status === 'pending');
  const confirmedCount  = bookings.filter(b => b.status === 'confirmed').length;
  const publishedVehicles = vehicles.filter(v => v.published).length;
  const monthlyRevenue = payments?.summary?.grossTotal || 0;

  // Dynamic trends computed from loaded data
  const calcTrend = (curr, prev) => {
    const dir = curr >= prev ? 'up' : 'down';
    if (prev === 0) return { dir, value: curr > 0 ? `+${curr}` : '0' };
    const pct = Math.round(((curr - prev) / prev) * 100);
    return { dir, value: `${pct >= 0 ? '+' : ''}${pct}%` };
  };
  const _now = new Date();
  const _thisMonthStart = new Date(_now.getFullYear(), _now.getMonth(), 1);
  const _lastMonthStart = new Date(_now.getFullYear(), _now.getMonth() - 1, 1);
  const _lastMonthEnd   = new Date(_now.getFullYear(), _now.getMonth(), 0, 23, 59, 59, 999);
  const confirmedThisMonth = bookings.filter(b => b.status === 'confirmed' && b.createdAt && new Date(b.createdAt) >= _thisMonthStart).length;
  const confirmedLastMonth = bookings.filter(b => b.status === 'confirmed' && b.createdAt && new Date(b.createdAt) >= _lastMonthStart && new Date(b.createdAt) <= _lastMonthEnd).length;
  const confirmedTrend = !loading ? calcTrend(confirmedThisMonth, confirmedLastMonth) : undefined;
  const revSeries = payments?.revenueSeries || [];
  const _half = Math.floor(revSeries.length / 2);
  const _revFirst = revSeries.slice(0, _half).reduce((s, r) => s + Number(r.value || r.v || 0), 0);
  const _revSecond = revSeries.slice(_half).reduce((s, r) => s + Number(r.value || r.v || 0), 0);
  const revenueTrend = !loading && revSeries.length >= 4 ? calcTrend(_revSecond, _revFirst) : undefined;

  const sparkData = payments?.revenueSeries
    ? payments.revenueSeries.map(r => {
        const val = Number(r.value || r.v || 0);
        return isNaN(val) || val === Infinity ? 0 : val;
      }).slice(-7)  // Last 7 days
    : [];
  const now15 = new Date(Date.now() + 15 * 86400000);
  const upcomingItems = bookings
    .filter(b => b.status === 'confirmed' && b.pickupAt
      && new Date(b.pickupAt) >= new Date()
      && new Date(b.pickupAt) <= now15)
    .sort((a, b) => new Date(a.pickupAt) - new Date(b.pickupAt));

  // Dot color: green (today) → lime (1-2j) → yellow (3-5j) → amber (6-15j)
  const dotColor = (daysAway) => {
    if (daysAway === 0) return '#059669'; // green
    if (daysAway <= 2)  return '#84cc16'; // lime
    if (daysAway <= 5)  return '#eab308'; // yellow
    return '#f97316';                     // amber
  };

  // Group same-day pickups so the dashboard shows one date entry with all the vehicles under it
  const upcomingGroups = [];
  upcomingItems.forEach(u => {
    const pickupDate = new Date(u.pickupAt);
    const key = pickupDate.toDateString();
    let group = upcomingGroups[upcomingGroups.length - 1];
    if (!group || group.key !== key) {
      const todayKey = new Date().toDateString();
      const tomorrowKey = new Date(Date.now() + 86400000).toDateString();
      const isToday = key === todayKey;
      const isTomorrow = key === tomorrowKey;
      const msInDay = 86400000;
      const daysAway = Math.round((pickupDate - new Date()) / msInDay);
      const daysLabel = isToday || isTomorrow ? null : t('dash.inDays', { n: daysAway });
      const when = isToday ? t('dash.today') : isTomorrow ? t('dash.tomorrow')
        : pickupDate.toLocaleDateString(l1Locale(), { weekday: 'short', day: 'numeric', month: 'short' });
      group = { key, when, isToday, daysAway, daysLabel, color: dotColor(daysAway), items: [] };
      upcomingGroups.push(group);
    }
    group.items.push(u);
  });

  const displayName = currentUser
    ? (currentUser.firstName ? `${currentUser.firstName} ${currentUser.lastName || ''}`.trim() : t('dash.lessor'))
    : t('dash.lessor');
  // Locale suivie : `'fr-FR'` était codé en dur, laissant la date du jour en français au-dessus
  // d'un écran par ailleurs traduit.
  const today = new Date().toLocaleDateString(l1Locale(), { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' });

  const confirm = async (b) => {
    setActionLoading(b.id || b.ref);
    try {
      await API.lessor.updateBookingStatus(b.id, 'confirmed');
      setBookings(bs => bs.map(x => (x.id === b.id || x.ref === b.ref) ? { ...x, status: 'confirmed' } : x));
      toast({ variant: 'success', title: t('book.confirmed'), desc: `${b.ref} · ${b.renter?.name}` });
    } catch (ex) {
      toast({ variant: 'error', title: t('common.error'), desc: ex.message });
    } finally {
      setActionLoading(null);
    }
  };

  const doReject = async (reason) => {
    setActionLoading(rejecting.id || rejecting.ref);
    try {
      await API.lessor.updateBookingStatus(rejecting.id, 'rejected', reason);
      setBookings(bs => bs.map(x => (x.id === rejecting.id || x.ref === rejecting.ref) ? { ...x, status: 'rejected' } : x));
      toast({ variant: 'error', title: t('book.rejectedToast'), desc: rejecting.ref });
    } catch (ex) {
      toast({ variant: 'error', title: t('common.error'), desc: ex.message });
    } finally {
      setActionLoading(null);
      setRejecting(null);
    }
  };

  return (
    <div className="page-wide">
      <div className="toolbar">
        <div className="page-greeting">
          <h2>Bonjour, {displayName} 👋</h2>
          <div className="gdate">{today}</div>
        </div>
      </div>

      {!loading && lessorProfile && lessorProfile.status === 'pending' && (
        <div style={{
          background: 'var(--amber-50, #fffbeb)', border: '1px solid var(--amber-300, #fcd34d)',
          borderRadius: 12, padding: '14px 20px', marginBottom: 20,
          display: 'flex', alignItems: 'center', gap: 14,
        }}>
          <Icon name="alert" size={20} style={{ color: 'var(--amber-600, #d97706)', flexShrink: 0 }} />
          <div style={{ flex: 1 }}>
            <div style={{ fontWeight: 700, fontSize: 14, color: 'var(--amber-800, #92400e)' }}>
              {t('dash.finishSetupTitle')}
            </div>
            <div style={{ fontSize: 13, color: 'var(--amber-700, #b45309)', marginTop: 2 }}>
              {t('dash.finishSetupBody')}
            </div>
          </div>
          <Button variant="secondary" size="sm" icon="chevRight" onClick={() => navigate('setup')}>
            {t('dash.complete')}
          </Button>
        </div>
      )}

      <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={t('dash.loadFailed')} action={<Button variant="secondary" icon="refresh" onClick={load}>{t('common.retry')}</Button>} /></div>
            : <>
              <StatCard icon="clock" color="amber"
                value={pendingRequests.length}
                label={t('dash.pendingRequests')}
                meta={t('dash.urgentCount', { n: pendingRequests.filter(b => b.createdMin > 30).length })}
                trend={pendingRequests.length > 0 ? { dir: 'up', value: `+${pendingRequests.length}` } : undefined} />
              <StatCard icon="calendarCheck" color="green"
                value={confirmedCount}
                label={t('dash.confirmedTitle')}
                meta={t('dash.vsLastMonth')}
                trend={confirmedTrend} />
              <StatCard icon="car" color="blue"
                value={publishedVehicles}
                label={t('dash.publishedVehicles')}
                meta={t('dash.seeFleet')} />
              <StatCard icon="wallet" color="violet"
                value={KD.fmtNum(Math.round(monthlyRevenue / 1000)) + ' k'}
                label={t('dash.monthRevenue')}
                meta={new Date().toLocaleDateString(l1Locale(), { month: 'long', year: 'numeric' })}
                trend={revenueTrend}
                spark={{ data: sparkData, color: '#7c3aed' }} />
            </>}
      </div>

      <div className="grid-2" style={{ gridTemplateColumns: '1.5fr 1fr' }}>
        {/* Pending requests */}
        <Card>
          <CardHead title={t('dash.pendingRequests')}
            sub={loading ? '…' : t('dash.toProcess', { n: pendingRequests.length })}
            icon={{ name: 'clock', color: 'amber' }}
            right={<Button size="sm" variant="ghost" iconRight="arrowUpRight" onClick={() => navigate('bookings')}>{t('dash.seeAll')}</Button>} />
          <div className="card-pad" style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
            {loading && [0,1,2].map(i => <div key={i} className="req"><Sk w={42} h={42} r={99} /><div style={{ flex: 1 }}><Sk w="40%" h={14} /><Sk w="60%" h={12} style={{ marginTop: 8 }} /></div></div>)}
            {!loading && err && <EmptyState variant="err" title={t('dash.loadError')} action={<Button variant="secondary" icon="refresh" onClick={load}>{t('common.retry')}</Button>} />}
            {!loading && !err && pendingRequests.length === 0 && <EmptyState variant="ok" icon="check" title={t('dash.allUpToDate')} desc={t('dash.noPending')} />}
            {!loading && !err && pendingRequests.map(b => (
              <div key={b.id || b.ref} className={`req ${b.createdMin > 30 ? 'urgent' : ''}`}>
                <Avatar name={b.renter?.name || '?'} color={b.renter?.color} size={44} />
                <div className="req-body">
                  <div className="req-top">
                    <span className="n">{b.renter?.name}</span>
                    {b.createdMin > 30 && <span className="urgent-tag"><Icon name="clock" size={11} />{t('dash.waitingMinutes', { n: b.createdMin })}</span>}
                    <span className="ref mono">{b.ref}</span>
                  </div>
                  <div className="req-veh">{b.vehicleName || '—'}</div>
                  <div className="req-meta">
                    <Icon name="calendar" size={14} /><span>{b.start} → {b.end}</span>
                    <span className="sep" /><span>{b.days} j</span>
                    <span className="sep" /><span className="amt">{KD.fmtDA(b.amount)}</span>
                  </div>
                  <div className="req-actions">
                    <Button size="sm" variant="danger" icon="x" loading={actionLoading === (b.id || b.ref)} onClick={() => setRejecting(b)}>{t('book.reject')}</Button>
                    <Button size="sm" variant="success" icon="check" loading={actionLoading === (b.id || b.ref)} onClick={() => confirm(b)}>{t('book.confirm')}</Button>
                  </div>
                </div>
              </div>
            ))}
          </div>
        </Card>

        {/* Upcoming */}
        <Card>
          <CardHead title={t('dash.upcomingTitle')} icon={{ name: 'route', color: 'blue' }} />
          <div className="card-pad">
            {loading
              ? [0,1,2,3].map(i => <div key={i} style={{ display: 'flex', gap: 12, padding: '12px 0' }}><Sk w={11} h={11} r={99} /><div style={{ flex: 1 }}><Sk w="35%" h={11} /><Sk w="70%" h={13} style={{ marginTop: 8 }} /></div></div>)
              : upcomingGroups.length === 0
                ? <EmptyState variant="empty" icon="calendar" title={t('dash.noUpcoming')} desc={t('dash.freeSchedule')} />
                : <div className="tl">
                    {upcomingGroups.map((g, i) => (
                      <div key={g.key} className="tl-item">
                        <div className="tl-rail">
                          <div
                            className={`tl-dot ${g.isToday ? 'today' : ''}`}
                            style={g.isToday ? {} : { background: g.color }}
                          />
                          {i < upcomingGroups.length - 1 && <div className="tl-line" />}
                        </div>
                        <div className="tl-content">
                          <div className="tl-when" style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                            <span>{g.when}{g.items.length > 1 ? ` · ${g.items.length} véhicules` : ''}</span>
                            {g.daysLabel && (
                              <span style={{ fontSize: 10, fontWeight: 700, padding: '2px 7px', borderRadius: 99, background: g.color + '22', color: g.color, letterSpacing: '.02em' }}>
                                {g.daysLabel}
                              </span>
                            )}
                          </div>
                          {g.items.map(u => (
                            <div key={u.id || u.ref} className="tl-main">
                              <div>
                                <div className="m1">{u.vehicleName || '—'}</div>
                                <div className="m2">{u.renter?.name || '—'}</div>
                              </div>
                              <div className="tl-end"><Icon name="arrowUpRight" size={13} />{u.end}</div>
                            </div>
                          ))}
                        </div>
                      </div>
                    ))}
                  </div>}
          </div>
        </Card>
      </div>

      {/* Quick links */}
      <div className="qlinks" style={{ marginTop: 20 }}>
        {[
          { icon: 'car',      color: 'green', t: t('dash.addVehicle'),      s: t('dash.growFleet'),      go: 'vehicles' },
          { icon: 'calendar', color: 'blue',  t: t('dash.seeSchedule'),     s: t('dash.upcomingRentals'), go: 'calendar' },
        ].map((q, i) => (
          <button key={i} className="qlink" onClick={() => navigate(q.go)}>
            <div className={`ql-ico sc-ico ${q.color}`}><Icon name={q.icon} size={22} /></div>
            <div><div className="ql-t">{q.t}</div><div className="ql-s">{q.s}</div></div>
            <span className="ql-arrow"><Icon name="chevRight" size={20} /></span>
          </button>
        ))}
      </div>

      {rejecting && <RejectModal title={t('book.rejectRequest')} who={`${rejecting.renter?.name} · ${rejecting.ref}`} onClose={() => setRejecting(null)} onConfirm={doReject} />}
    </div>
  );
}

// ============================================================
// LESSOR BOOKINGS LIST
// ============================================================
function LessorBookings({ navigate }) {
  const t = useT1();
  const toast = useToast();
  const [rows, setRows] = useS1([]);
  const [loading, setLoading] = useS1(true);
  const [err, setErr] = useS1(false);
  const [tab, setTab] = useS1('all');
  const [view, setView] = useS1('table');
  const [rejecting, setRejecting] = useS1(null);
  const [payingBooking, setPayingBooking] = useS1(null);
  const [sel, setSel] = useS1([]);
  const [search, setSearch] = useS1('');
  const [actionLoading, setActionLoading] = useS1(null);
  const [notFulfilling, setNotFulfilling] = useS1(null);
  // Tri par défaut : départ le plus proche en premier.
  const [sortBy, setSortBy] = useS1('pickup');
  const [sortDir, setSortDir] = useS1('asc');
  // Densité et colonnes visibles, persistées : un loueur qui a masqué « Frais de service »
  // ne veut pas la retrouver à chaque connexion.
  const [dense, setDense] = useS1(() => localStorage.getItem('uleez.bookings.dense') === '1');
  const [cols, setCols] = useS1(() => {
    try { return JSON.parse(localStorage.getItem('uleez.bookings.cols')) || {}; }
    catch { return {}; }
  });
  const [colsOpen, setColsOpen] = useS1(false);

  // Colonnes optionnelles : masquables sans jamais faire disparaître l'essentiel
  // (référence, locataire, véhicule, période, encaissé, statut, actions restent toujours là).
  const OPTIONAL_COLS = [
    { key: 'vehicle',  label: t('book.colVehicle') },
    { key: 'created',  label: t('book.colBookedOn') },
    { key: 'days',     label: t('book.colDuration') },
    { key: 'fee',      label: t('book.colServiceFee') },
    { key: 'net',      label: t('book.colNet') },
  ];
  const showCol = (k) => cols[k] !== false;
  const toggleCol = (k) => setCols(c => {
    const next = { ...c, [k]: c[k] === false };
    localStorage.setItem('uleez.bookings.cols', JSON.stringify(next));
    return next;
  });
  const toggleDense = () => setDense(d => {
    localStorage.setItem('uleez.bookings.dense', d ? '0' : '1');
    return !d;
  });

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

  const silentReload = () => {
    API.lessor.getBookings()
      .then(data => {
        if (Array.isArray(data)) setRows(data.map(API.normalizeBooking));
      })
      .catch(() => {});
  };

  useE1(() => {
    load();
    // Poll every 30 seconds when tab is visible
    const interval = setInterval(() => {
      if (!document.hidden) silentReload();
    }, 30000);
    return () => clearInterval(interval);
  }, []);

  const TABS = [
    { id: 'all',        label: t('book.tabAll') },
    { id: 'pending',    label: t('book.tabPending'), alert: true },
    { id: 'upcoming',   label: t('book.tabUpcoming') },
    { id: 'ongoing',    label: t('book.tabInProgress') },
    { id: 'confirmed',  label: t('book.tabConfirmed') },
    { id: 'completed',  label: t('book.tabCompleted') },
    { id: 'cancelled',  label: t('book.tabCancelled') },
  ];

  // "À venir" / "En cours" are DATE-derived (not the backend `in_progress` status, which is
  // never set): upcoming = confirmed/pending starting later; ongoing = confirmed rental whose
  // slot has begun but not yet ended.
  const now = Date.now();
  const isUpcoming = (b) => b.pickupAt && new Date(b.pickupAt).getTime() > now && ['confirmed', 'pending'].includes(b.status);
  const isOngoing = (b) => b.pickupAt && b.returnAt
    && new Date(b.pickupAt).getTime() <= now && now <= new Date(b.returnAt).getTime()
    && b.status === 'confirmed';
  const matchTab = (b) => {
    if (tab === 'all') return true;
    if (tab === 'upcoming') return isUpcoming(b);
    if (tab === 'ongoing') return isOngoing(b);
    return b.status === tab;
  };

  // ── Tri ────────────────────────────────────────────────────────────────────
  //
  // Le tri par défaut reste « départ le plus proche d'abord » — c'est la question que se pose
  // un loueur en ouvrant l'écran. Mais il est désormais un ÉTAT, pas un `.sort()` figé : les
  // en-têtes portaient une flèche décorative sans aucun `onClick`, donc rien n'était triable.
  const SORTERS = {
    ref:      (b) => b.ref || '',
    renter:   (b) => (b.renter?.name || '').toLowerCase(),
    vehicle:  (b) => (b.vehicleName || '').toLowerCase(),
    created:  (b) => new Date(b.createdAt || 0).getTime(),
    pickup:   (b) => new Date(b.pickupAt || 0).getTime(),
    days:     (b) => Number(b.days) || 0,
    amount:   (b) => Number(b.amount) || 0,
    net:      (b) => Number(b.accounting?.lessorNet ?? 0),
    settled:  (b) => new Date(b.settledAt || 0).getTime(),
    status:   (b) => b.status || '',
  };

  const sorted = (list) => {
    const pick = SORTERS[sortBy] || SORTERS.pickup;
    const dir = sortDir === 'asc' ? 1 : -1;
    return [...list].sort((a, b) => {
      const va = pick(a), vb = pick(b);
      if (va === vb) return 0;
      return (va > vb ? 1 : -1) * dir;
    });
  };

  const filtered = sorted(rows
    .filter(matchTab)
    .filter(b => !search || b.ref?.toLowerCase().includes(search.toLowerCase()) || b.renter?.name?.toLowerCase().includes(search.toLowerCase())));

  const toggleSort = (key) => {
    if (sortBy === key) { setSortDir(d => d === 'asc' ? 'desc' : 'asc'); return; }
    setSortBy(key);
    // Les dates et les montants sont plus utiles en décroissant au premier clic (le plus
    // récent, le plus gros) ; le texte en croissant.
    setSortDir(['created', 'settled', 'amount', 'net', 'days'].includes(key) ? 'desc' : 'asc');
  };

  const Th = ({ k, children, align, width }) => (
    <th className="sortable" style={{ textAlign: align, width }} onClick={() => toggleSort(k)} title="Trier">
      <span className="th-in" style={align === 'right' ? { justifyContent: 'flex-end' } : undefined}>
        {children}
        <Icon name={sortBy === k ? (sortDir === 'asc' ? 'chevUp' : 'chevDown') : 'chevDown'}
              size={13} style={{ opacity: sortBy === k ? 1 : 0.25 }} />
      </span>
    </th>
  );

  const count = (id) => {
    if (id === 'all') return rows.length;
    if (id === 'upcoming') return rows.filter(isUpcoming).length;
    if (id === 'ongoing') return rows.filter(isOngoing).length;
    return rows.filter(b => b.status === id).length;
  };
  const toggleSel = (id) => setSel(s => s.includes(id) ? s.filter(x => x !== id) : [...s, id]);

  const act = async (b, status, msg, variant, reason) => {
    const key = b.id || b.ref;
    setActionLoading(key);
    try {
      await API.lessor.updateBookingStatus(b.id, status, reason);
      setRows(rs => rs.map(x => (x.id === b.id || x.ref === b.ref) ? { ...x, status } : x));
      toast({ variant, title: msg, desc: b.ref });
    } catch (ex) {
      toast({ variant: 'error', title: t('common.error'), desc: ex.message });
    } finally {
      setActionLoading(null);
    }
  };

  const hide = async (b) => {
    const key = b.id || b.ref;
    setActionLoading(key);
    try {
      await API.lessor.hideBooking(b.id);
      setRows(rs => rs.filter(x => x.id !== b.id));
      toast({ variant: 'info', title: t('book.deleted'), desc: b.ref });
    } catch (ex) {
      toast({ variant: 'error', title: t('common.error'), desc: ex.message });
    } finally {
      setActionLoading(null);
    }
  };

  const doReject = async (reason) => {
    await act(rejecting, 'rejected', t('book.rejectedBooking'), 'error', reason);
    setRejecting(null);
  };

  const actionsFor = (b) => {
    const key = b.id || b.ref;
    const busy = actionLoading === key;
    const payBtn = b.payment !== 'paid' && ['confirmed', 'completed'].includes(b.status)
      ? <Button size="sm" variant="secondary" icon="banknote" onClick={(e) => { e.stopPropagation(); setPayingBooking(b); }}>{t('book.payment')}</Button>
      : null;
    if (b.status === 'pending') return <div className="row" style={{ gap: 7 }}>
      <Button size="sm" variant="danger" icon="x" loading={busy} onClick={(e) => { e.stopPropagation(); setRejecting(b); }}>{t('book.reject')}</Button>
      <Button size="sm" variant="success" icon="check" loading={busy} onClick={(e) => { e.stopPropagation(); act(b, 'confirmed', t('book.confirmed'), 'success'); }}>{t('book.confirm')}</Button>
    </div>;
    if (b.status === 'confirmed') return <div className="row" style={{ gap: 7 }}>
      {payBtn}
      <Button size="sm" variant="secondary" loading={busy} onClick={(e) => { e.stopPropagation(); act(b, 'cancelled', t('book.cancelled'), 'info'); }}>{t('book.cancel')}</Button>
      <Button size="sm" variant="success" icon="check" loading={busy} onClick={(e) => { e.stopPropagation(); act(b, 'completed', t('book.completed'), 'success'); }}>{t('book.finish')}</Button>
    </div>;
    if (b.status === 'in_progress') return <div className="row" style={{ gap: 7 }}>
      {payBtn}
      <Button size="sm" variant="success" icon="check" loading={busy} onClick={(e) => { e.stopPropagation(); act(b, 'completed', t('book.completed'), 'success'); }}>{t('book.finish')}</Button>
    </div>;
    if (b.status === 'completed') return <div className="row" style={{ gap: 7 }}>
      {payBtn}
      {/* Contrepartie de la présomption d'encaissement : si la location n'a pas eu lieu, le
          loueur doit pouvoir le dire. Proposé uniquement quand le SERVEUR l'autorise
          (encaissement présumé + moins de 30 jours) — la règle n'est pas re-dérivée ici. */}
      {b.canDeclareNotFulfilled && (
        <Button size="sm" variant="secondary" icon="alert" loading={busy}
                onClick={(e) => { e.stopPropagation(); setNotFulfilling(b); }}>{t('book.notFulfilled')}</Button>
      )}
      <Button size="sm" variant="secondary" icon="trash" loading={busy} onClick={(e) => { e.stopPropagation(); hide(b); }}>{t('book.delete')}</Button>
      <Button size="sm" variant="ghost" iconRight="chevRight" onClick={(e) => { e.stopPropagation(); navigate('booking-detail', { id: b.id, ref: b.ref }); }}>{t('book.detail')}</Button>
    </div>;
    if (['cancelled', 'rejected'].includes(b.status)) return <div className="row" style={{ gap: 7 }}>
      <Button size="sm" variant="secondary" icon="trash" loading={busy} onClick={(e) => { e.stopPropagation(); hide(b); }}>{t('book.delete')}</Button>
      <Button size="sm" variant="ghost" iconRight="chevRight" onClick={(e) => { e.stopPropagation(); navigate('booking-detail', { id: b.id, ref: b.ref }); }}>{t('book.detail')}</Button>
    </div>;
    return <Button size="sm" variant="ghost" iconRight="chevRight" onClick={(e) => { e.stopPropagation(); navigate('booking-detail', { id: b.id, ref: b.ref }); }}>{t('book.detail')}</Button>;
  };

  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" />
        <Button variant="secondary" icon="refresh" size="sm" onClick={load}>Actualiser</Button>
      </div>

      <div className="toolbar">
        <div className="header-search" style={{ display: 'flex', width: 280, background: 'var(--surface)', border: '1px solid var(--line)' }}>
          <Icon name="search" size={17} /><input placeholder={t('book.searchPlaceholder')} value={search} onChange={e => setSearch(e.target.value)} />
        </div>
        <div className="spacer" />
        {view === 'table' && (
          <div style={{ position: 'relative' }}>
            <Button variant="secondary" icon="sliders" size="sm" onClick={() => setColsOpen(o => !o)}>{t('book.columns')}</Button>
            {colsOpen && (
              <>
                <div style={{ position: 'fixed', inset: 0, zIndex: 40 }} onClick={() => setColsOpen(false)} />
                <div className="col-menu">
                  {OPTIONAL_COLS.map(c => (
                    <label key={c.key} className="col-menu-row">
                      <Checkbox checked={showCol(c.key)} onChange={() => toggleCol(c.key)} />
                      <span>{c.label}</span>
                    </label>
                  ))}
                  <div className="col-menu-sep" />
                  <label className="col-menu-row">
                    <Checkbox checked={dense} onChange={toggleDense} />
                    <span>{t('book.compactView')}</span>
                  </label>
                </div>
              </>
            )}
          </div>
        )}
        <div className="seg">
          <button className={view === 'table' ? 'on' : ''} onClick={() => setView('table')}><Icon name="list" size={15} /></button>
          <button className={view === 'cards' ? 'on' : ''} onClick={() => setView('cards')}><Icon name="grid" size={15} /></button>
        </div>
        <Dropdown
          trigger={<Button variant="secondary" icon="download" size="sm">{t('common.export')}</Button>}
          items={[
            { label: t('book.exportExcel'), onClick: () => API.lessor.exportBookings(tab, 'xlsx').catch(() => toast({ title: t('book.exportFailed'), variant: 'error' })) },
            { label: t('book.exportPdf'), onClick: () => API.lessor.exportBookings(tab, 'pdf').catch(() => toast({ title: t('book.exportFailed'), variant: 'error' })) },
          ]}
        />
      </div>

      <Card>
        {loading && <div className="card-pad"><TableSkeleton cols={10} /></div>}
        {!loading && err && <EmptyState variant="err" title={t('book.loadFailed')} action={<Button variant="secondary" icon="refresh" onClick={load}>{t('common.retry')}</Button>} />}
        {!loading && !err && filtered.length === 0 && <EmptyState variant="empty" icon="clipboard" title={t('book.none')} desc={t('book.noneDesc')} />}
        {!loading && !err && filtered.length > 0 && (view === 'table'
          ? <div className="tbl-wrap"><table className={`tbl tbl-fixed tbl-scroll${dense ? ' tbl-dense' : ''}`}>
              <thead><tr>
                <th style={{ width: 38 }}>
                  <Checkbox checked={sel.length === filtered.length && filtered.length > 0}
                    onChange={() => setSel(sel.length === filtered.length ? [] : filtered.map(b => b.id || b.ref))} />
                </th>
                {/* Colonnes composites : 13 colonnes tassaient tout. La référence porte la date
                    de réservation en ligne secondaire, l'encaissé porte le net et les frais. */}
                <Th k="ref" width={150}>{t('book.colBooking')}</Th>
                <Th k="renter" width={170}>{t('book.colRenter')}</Th>
                {showCol('vehicle') && <Th k="vehicle" width={160}>{t('book.colVehicle')}</Th>}
                <Th k="pickup" width={150}>{t('book.colPeriod')}</Th>
                {showCol('days') && <Th k="days" width={70} align="center">{t('book.colDuration')}</Th>}
                <Th k="amount" width={140} align="right">{t('book.colCollected')}</Th>
                {showCol('net') && <Th k="net" width={120} align="right">{t('book.colNet')}</Th>}
                <Th k="status" width={130}>{t('book.colStatus')}</Th>
                {/* 200 px ne suffisaient pas : la branche « terminée » peut porter 4 boutons
                    (Paiement / Non réalisée / Supprimer / Détail), soit ~380 px. */}
                <th style={{ textAlign: 'right', width: 400 }}>{t('book.colActions')}</th>
              </tr></thead>
              <tbody>
                {filtered.map(b => (
                  <tr key={b.id || b.ref} className="clickable" onClick={() => navigate('booking-detail', { id: b.id, ref: b.ref })}>
                    <td><Checkbox checked={sel.includes(b.id || b.ref)} onChange={() => toggleSel(b.id || b.ref)} /></td>

                    <td>
                      <div className="cellstack">
                        <div className="mono strong">{b.ref}</div>
                        {showCol('created') && (
                          <div className="sub" title={b.createdAt ? new Date(b.createdAt).toLocaleString('fr-FR') : ''}>
                            {t('book.bookedOn', { date: b.createdAt ? new Date(b.createdAt).toLocaleDateString(l1Locale(), { day: 'numeric', month: 'short', year: '2-digit' }) : '—' })}
                          </div>
                        )}
                      </div>
                    </td>

                    <td>
                      <div className="celluser">
                        <Avatar name={b.renter?.name || '?'} color={b.renter?.color} size={30} />
                        <div className="cu-meta"><div className="n truncate" title={b.renter?.name}>{b.renter?.name}</div></div>
                      </div>
                    </td>

                    {showCol('vehicle') && (
                      <td><div className="truncate" title={b.vehicleName || ''}>{b.vehicleName || '—'}</div></td>
                    )}

                    <td>
                      <div className="cellstack">
                        <div className="nowrap">{b.start} → {b.end}</div>
                        {b.settledAt && (
                          <div className="sub" title={t('book.settledOnHint')}>
                            {t('book.settledOn', { date: new Date(b.settledAt).toLocaleDateString(l1Locale(), { day: 'numeric', month: 'short' }) })}
                          </div>
                        )}
                      </div>
                    </td>

                    {showCol('days') && <td style={{ textAlign: 'center' }}>{t('book.days', { n: b.days })}</td>}

                    <td className="cellnum" style={{ textAlign: 'right' }}>
                      <div className="cellstack" style={{ alignItems: 'flex-end' }}>
                        <div className="strong">{KD.fmtDA(b.amount)}</div>
                        {/* Les frais de service sont de l'argent que le client paie à Uleez À
                            TRAVERS le loueur. Les afficher évite que le reversement de fin de
                            mois arrive comme une surprise. */}
                        {showCol('fee') && b.serviceFee > 0 && (
                          <div className="sub" style={{ color: 'var(--amber-700)' }}
                               title={t('book.serviceFeeHint')}>
                            {t('book.ofWhichFees', { amount: KD.fmtDA(b.serviceFee) })}
                          </div>
                        )}
                      </div>
                    </td>

                    {showCol('net') && (
                      <td className="cellnum" style={{ textAlign: 'right', fontWeight: 800, color: 'var(--brand-700)' }}>
                        {b.accounting ? KD.fmtDA(b.accounting.lessorNet) : '—'}
                      </td>
                    )}

                    <td>
                      <div className="cellstack" style={{ gap: 4 }}>
                        <StatusBadge map={KD.BOOKING_STATUS} value={b.status} />
                        {b.paymentPresumed
                          ? <span title={t('book.presumedHint')}>
                              <Badge cls="amber">{t('book.presumed')}</Badge>
                            </span>
                          : <StatusBadge map={KD.PAYMENT_STATUS} value={b.payment} />}
                      </div>
                    </td>

                    <td className="cellactions" onClick={(e) => e.stopPropagation()} style={{ textAlign: 'right' }}>{actionsFor(b)}</td>
                  </tr>
                ))}
              </tbody>
            </table></div>
          : <div className="card-pad" style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill,minmax(320px,1fr))', gap: 14 }}>
              {filtered.map(b => (
                <div key={b.id || b.ref} className="req" style={{ cursor: 'pointer' }} onClick={() => navigate('booking-detail', { id: b.id, ref: b.ref })}>
                  <Avatar name={b.renter?.name || '?'} color={b.renter?.color} size={42} />
                  <div className="req-body">
                    <div className="req-top"><span className="n">{b.renter?.name}</span><span className="ref mono">{b.ref}</span></div>
                    <div className="req-veh">{b.vehicleName || '—'}</div>
                    <div className="req-meta"><Icon name="calendar" size={13} />{b.start} → {b.end}<span className="sep" /><span className="amt">{KD.fmtDA(b.amount)}</span></div>
                    {b.serviceFee > 0 && (
                      <div className="req-meta" style={{ color: 'var(--amber-700)' }}>
                        <Icon name="info" size={13} />
                        dont {KD.fmtDA(b.serviceFee)} de frais de service à reverser
                      </div>
                    )}
                    <div style={{ marginTop: 10 }}><StatusBadge map={KD.BOOKING_STATUS} value={b.status} /></div>
                  </div>
                </div>
              ))}
            </div>)}
        {/* Le pagineur affichait une page « 1 » en dur, sans aucune pagination derrière : il
            laissait croire à d'autres pages inexistantes. La liste n'est pas paginée côté
            client, donc le pied de page se contente de compter. */}
        {!loading && !err && filtered.length > 0 && view === 'table' &&
          <div className="tbl-foot">
            <span>{sel.length > 0 ? t('book.selectedCount', { n: sel.length }) : t('book.bookingCount', { n: filtered.length })}</span>
          </div>}
      </Card>

      {rejecting && <RejectModal title={t('book.rejectBooking')} who={`${rejecting.renter?.name} · ${rejecting.ref}`} onClose={() => setRejecting(null)} onConfirm={doReject} />}
      {payingBooking && (
        <RecordPaymentModal
          booking={payingBooking}
          onClose={() => setPayingBooking(null)}
          onSuccess={() => {
            setRows(rs => rs.map(x => x.id === payingBooking.id ? { ...x, payment: 'paid' } : x));
            setPayingBooking(null);
          }}
        />
      )}
      {notFulfilling && (
        <NotFulfilledModal
          booking={notFulfilling}
          onClose={() => setNotFulfilling(null)}
          onSuccess={() => {
            setRows(rs => rs.map(x => x.id === notFulfilling.id
              ? { ...x, status: 'cancelled', payment: 'none', paymentPresumed: false, settledAt: null, canDeclareNotFulfilled: false }
              : x));
            setNotFulfilling(null);
          }}
        />
      )}
    </div>
  );
}

// ─── Modal : Enregistrer un paiement sur place ───────────────────────────────
// The amount is no longer typed in: the server records the booking's own total, so a free field
// here would only have been a way to under-declare the takings — and to shrink the commission
// and service fee owed back to Uleez. This is now a confirmation, not a form.
function RecordPaymentModal({ booking, onClose, onSuccess }) {
  const t = useT1();
  const toast = useToast();
  const [saving, setSaving] = useS1(false);

  const submit = async () => {
    setSaving(true);
    try {
      await API.lessor.recordOnSitePayment(booking.id);
      toast({ variant: 'success', title: t('pay.recorded'), desc: `${KD.fmtDA(booking.amount)} · ${booking.ref}` });
      onSuccess();
    } catch (ex) {
      toast({ variant: 'error', title: t('common.error'), desc: ex.message });
    } finally {
      setSaving(false);
    }
  };

  return (
    <Modal title={t('pay.recordTitle')} desc={`${booking.ref} · ${booking.renter?.name}`}
      icon={{ name: 'banknote', color: 'green' }} onClose={onClose}
      footer={<>
        <Button variant="secondary" onClick={onClose}>{t('common.cancel')}</Button>
        <Button variant="primary" icon="check" loading={saving} onClick={submit}>{t('pay.confirmCollection')}</Button>
      </>}>
      <Field label={t('pay.amountToCollect')}>
        <div style={{ fontSize: 26, fontWeight: 800, color: 'var(--brand-700, #047857)' }}>{KD.fmtDA(booking.amount)}</div>
        <div className="muted" style={{ fontSize: 12.5, marginTop: 4 }}>
          {t('pay.totalNote')}
        </div>
      </Field>
      <div className="kv" style={{ marginTop: 8 }}><span className="k">{t('pay.method')}</span><span className="v">{t('pay.cashOnPickup')}</span></div>
      <div className="kv"><span className="k">{t('pay.reference')}</span><span className="v mono">{booking.ref}</span></div>
    </Modal>
  );
}

// ─── Modal : déclarer une location non réalisée ──────────────────────────────
//
// Contrepartie de la présomption d'encaissement. Une location dont la date de fin est passée est
// comptabilisée comme encaissée au bout de 48 h — parce qu'en paiement à la remise, l'oubli du
// bouton est bien plus fréquent que la location fantôme. Mais si le client s'est désisté, le
// loueur doit pouvoir le dire, et ce geste annule l'argent : d'où le motif obligatoire, tracé.
//
// Ne s'affiche que sur un encaissement PRÉSUMÉ. Un encaissement que le loueur a confirmé
// lui-même ne peut être annulé que par le support — sinon la confirmation ne vaut rien.
function NotFulfilledModal({ booking, onClose, onSuccess }) {
  const t = useT1();
  const toast = useToast();
  const [saving, setSaving] = useS1(false);
  const [reason, setReason] = useS1('');
  const tooShort = reason.trim().length < 10;

  const submit = async () => {
    if (tooShort) return;
    setSaving(true);
    try {
      await API.lessor.declareNotFulfilled(booking.id, reason.trim());
      toast({ variant: 'info', title: t('book.notFulfilledToast'), desc: booking.ref });
      onSuccess();
    } catch (ex) {
      toast({ variant: 'error', title: t('common.error'), desc: ex.message });
    } finally {
      setSaving(false);
    }
  };

  return (
    <Modal title={t('nf.title')} desc={`${booking.ref} · ${booking.renter?.name}`}
      icon={{ name: 'alert', color: 'amber' }} onClose={onClose}
      footer={<>
        <Button variant="secondary" onClick={onClose}>{t('common.cancel')}</Button>
        <Button variant="danger" icon="x" loading={saving} disabled={tooShort} onClick={submit}>
          {t('nf.confirmCancel')}
        </Button>
      </>}>
      <div className="muted" style={{ fontSize: 13, lineHeight: 1.55, marginBottom: 14 }}>
        {booking.settledAt
          ? t('nf.explainOn', {
              date: new Date(booking.settledAt).toLocaleDateString(window.I18N && I18N.lang === 'ar' ? 'ar-DZ' : window.I18N && I18N.lang === 'en' ? 'en-GB' : 'fr-FR'),
              amount: KD.fmtDA(booking.amount),
            })
          : t('nf.explain', { amount: KD.fmtDA(booking.amount) })}
      </div>
      <Field label={t('nf.reasonRequired')}>
        <Textarea rows={3} value={reason} onChange={e => setReason(e.target.value)}
          placeholder={t('nf.reasonHint')} />
        <div className="muted" style={{ fontSize: 12, marginTop: 4 }}>
          {tooShort ? t('nf.charsLeft', { n: 10 - reason.trim().length }) : t('nf.reasonKept')}
        </div>
      </Field>
    </Modal>
  );
}

function TableSkeleton({ cols = 6, rows = 6 }) {
  return (
    <div>
      <div style={{ display: 'flex', gap: 16, padding: '4px 0 14px' }}>{Array.from({ length: cols }).map((_, i) => <Sk key={i} w={`${100 / cols}%`} h={11} />)}</div>
      {Array.from({ length: rows }).map((_, r) => (
        <div key={r} style={{ display: 'flex', gap: 16, padding: '13px 0', borderTop: '1px solid var(--line)', alignItems: 'center' }}>
          {Array.from({ length: cols }).map((_, i) => <Sk key={i} w={`${100 / cols}%`} h={14} />)}
        </div>
      ))}
    </div>
  );
}

Object.assign(window, { LessorDashboard, LessorBookings, RejectModal, RecordPaymentModal, NotFulfilledModal, StateSeg, TableSkeleton });
