/* Uleez — Lessor screens part 2: booking detail, calendar, fleet */
const { useState: useS2, useEffect: useE2, useRef: useR2 } = React;

// ============================================================
// BOOKING DETAIL (+ messaging)
// ============================================================
// Renders a PRIVATE image (inspection photo, chat attachment, encrypted KYC document)
// fetched with the auth token as a blob. Plain <img src> can't send the Bearer header,
// so we resolve an object URL first.
//
// `fetcher` lets the same component serve both private surfaces: chat attachments go
// through /support/attachments/:id, identity documents through /documents/:id/content
// (which decrypts server-side after an ownership check). Documents are no longer readable
// from the public /uploads prefix at all — that is the whole point.
function AuthedImg({ path, alt, style, fetcher, onError }) {
  const t = window.I18N ? I18N.useT() : ((k) => k);
  const [url, setUrl] = useS2(null);
  const [failed, setFailed] = useS2(false);
  useE2(() => {
    let obj = null;
    let cancelled = false;
    setUrl(null); setFailed(false);
    const load = fetcher || API.support.attachmentObjectUrl;
    Promise.resolve(load(path))
      .then(u => {
        if (cancelled) { URL.revokeObjectURL(u); return; }
        obj = u; setUrl(u);
      })
      .catch((e) => { if (!cancelled) { setFailed(true); if (onError) onError(e); } });
    return () => { cancelled = true; if (obj) URL.revokeObjectURL(obj); };
  }, [path]);
  if (failed) {
    return <div style={{ ...style, background: 'var(--slate-100)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--muted)', fontSize: 12, textAlign: 'center', padding: 8 }}>{t('det.previewUnavailable')}</div>;
  }
  return url
    ? <img src={url} alt={alt} loading="lazy" style={style} />
    : <div style={{ ...style, background: 'var(--slate-100)' }} />;
}

function BookingDetail({ params, navigate }) {
  const t = window.I18N ? I18N.useT() : ((k) => k);
  const loc = (window.I18N ? I18N.lang : 'fr') === 'ar' ? 'ar-DZ'
    : (window.I18N ? I18N.lang : 'fr') === 'en' ? 'en-GB' : 'fr-FR';
  const toast = useToast();
  const bookingId = params?.id;
  const refParam  = params?.ref;

  const [booking, setBooking] = useS2(null);
  const [loading, setLoading] = useS2(true);
  const [err, setErr] = useS2(false);
  const [status, setStatus] = useS2(null);
  const [msgs, setMsgs] = useS2([]);
  const [ticketId, setTicketId] = useS2(null);
  // Message being quoted by the composer (null = plain message).
  const [replyTo, setReplyTo] = useS2(null);
  const [draft, setDraft] = useS2('');
  const [sending, setSending] = useS2(false);
  const [rejecting, setRejecting] = useS2(false);
  const [payingOpen, setPayingOpen] = useS2(false);
  const [deleting, setDeleting] = useS2(false);
  const [delLoading, setDelLoading] = useS2(false);
  const [actionLoading, setActionLoading] = useS2(null);
  const [documents, setDocuments] = useS2([]);
  const [activeTab, setActiveTab] = useS2('reservation');
  const [extLoading, setExtLoading] = useS2(null);
  const [inspections, setInspections] = useS2([]);
  const [peerTyping, setPeerTyping] = useS2(false);
  const bodyRef = useR2();
  const composerRef = useR2();

  // "Envoyer un message" used to navigate to the standalone messages page. That page is now
  // the emergency-only "Assistance client" tab and never held booking conversations anyway —
  // and this booking's thread is right there in the panel on this same screen.
  const focusComposer = () => {
    const box = composerRef.current;
    if (!box) return;
    box.scrollIntoView({ behavior: 'smooth', block: 'center' });
    const field = box.querySelector('textarea');
    if (field) field.focus();
  };
  // Heartbeat-based typing signal (see createTypingSignal in ui.jsx).
  const typingRef = useR2(null);
  if (!typingRef.current) typingRef.current = createTypingSignal();
  const peerTypingRef = useR2(null);
  useE2(() => () => typingRef.current && typingRef.current.dispose(), []);

  useE2(() => {
    if (!bookingId) { setErr(true); setLoading(false); return; }
    API.lessor.getBooking(bookingId)
      .then(data => {
        const b = API.normalizeBooking(data);
        setBooking(b);
        setStatus(b.status);
        setLoading(false);
        // Merge booking-scoped docs with the renter's KYC identity docs (CNI + permis),
        // which are shared once the client is verified — deduped by id.
        Promise.all([
          API.lessor.getBookingDocuments(bookingId).catch(() => []),
          API.lessor.getBookingCustomerDocuments(bookingId).catch(() => []),
        ]).then(([bookingDocs, kycDocs]) => {
          const byId = {};
          [...(Array.isArray(kycDocs) ? kycDocs : []), ...(Array.isArray(bookingDocs) ? bookingDocs : [])]
            .forEach(d => { if (d && d.id) byId[d.id] = d; });
          setDocuments(Object.values(byId));
        }).catch(() => setDocuments([]));
        // Load inspections (état des lieux) captured by the renter, if any.
        API.lessor.getInspection(bookingId)
          .then(res => setInspections(Array.isArray(res) ? res : (res ? [res] : [])))
          .catch(() => setInspections([]));
        // Load messages from the conversation linked to this booking
        return loadThread();
      })
      .catch(() => { setErr(true); setLoading(false); });
  }, [bookingId]);

  // (Re)charge le fil de cette réservation. Appelé au montage, quand le serveur annonce la
  // création du fil (`conversation:new`) et après chaque reconnexion : la page restait sinon
  // aveugle à un fil créé APRÈS son ouverture — d'où « les messages n'arrivent pas ».
  const loadThread = () => API.support.getByBooking(bookingId).catch(() => null).then(ticket => {
    if (!ticket || !ticket.id) return;
    setTicketId(ticket.id);
    setMsgs((ticket.messages || []).map(m => ({
      id: m.id,
      from: m.senderRole === 'lessor' ? 'me' : 'them',
      // Kept for groupAlbums: album rows only merge when sender AND time agree.
      senderId: m.senderId,
      createdAt: m.createdAt,
      text: m.body,
      // Set when the thread is encrypted but this browser holds no key for it — the
      // bubble renders locked rather than blank.
      undecryptable: m.undecryptable || false,
      location: messageLocation(m),
      attachment: m.attachment || null,
      replyTo: m.replyTo || null,
      reactions: m.reactions || [],
      t: new Date(m.createdAt).toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' }),
    })));
  });

  // Fil créé par l'autre bord (ou ouvert à l'avance par l'app du locataire) : on le charge et
  // l'effet temps réel ci-dessous rejoint sa salle. Resynchro aussi après chaque reconnexion —
  // les événements émis pendant la coupure ne sont pas rejoués par le serveur.
  useE2(() => {
    if (!bookingId) return;
    API.Realtime.connect();
    const offNew = API.Realtime.on('conversation:new', (e) => {
      if (e && e.bookingId === bookingId) loadThread();
    });
    const offReconnect = API.Realtime.onReconnect(() => loadThread());
    return () => { offNew(); offReconnect(); };
  }, [bookingId]);

  useE2(() => { if (bodyRef.current) bodyRef.current.scrollTop = bodyRef.current.scrollHeight; }, [msgs, peerTyping]);

  // Realtime: instant incoming messages for this booking's conversation.
  useE2(() => {
    if (!ticketId) return;
    setPeerTyping(false);
    API.Realtime.connect();
    API.Realtime.joinConversation(ticketId);
    const unsub = API.Realtime.on('message:new', async (raw) => {
      if (!raw || raw.ticketId !== ticketId) return;
      setPeerTyping(false);
      if (raw.senderRole === 'lessor') return; // our own echo
      // A realtime frame carries ciphertext like any other message — decrypt before render.
      const m = await API.support.decryptMessage(ticketId, raw);
      // Appended flat; groupAlbums (at render) merges the rows of an album into one bubble,
      // so nothing here has to know about albums.
      setMsgs(prev => [...prev, {
        id: m.id,
        from: 'them',
        senderId: m.senderId,
        createdAt: m.createdAt,
        text: m.body,
        undecryptable: m.undecryptable || false,
        location: messageLocation(m),
        attachment: m.attachment || null,
        replyTo: m.replyTo || null,
        reactions: m.reactions || [],
        t: m.createdAt ? new Date(m.createdAt).toLocaleTimeString(loc, { hour: '2-digit', minute: '2-digit' }) : t('asst.justNow'),
      }]);
    });
    // Reactions posted by the peer, live. The server has always emitted this event; nothing
    // listened to it, so a reaction only appeared on the next full reload.
    const unsubReaction = API.Realtime.on('message:reaction', (e) => {
      if (!e || e.ticketId !== ticketId) return;
      setMsgs(prev => prev.map(m => m.id === e.messageId
        ? { ...m, reactions: API.support.mergeReactions(m.reactions, e) }
        : m));
    });
    // Peer typing indicator (auto-clears if pings stop).
    const unsubTyping = API.Realtime.on('conversation:typing', (e) => {
      if (!e || e.ticketId !== ticketId) return;
      const typing = e.typing !== false;
      setPeerTyping(typing);
      if (peerTypingRef.current) clearTimeout(peerTypingRef.current);
      // Longer than two heartbeats: the peer re-pings every 3 s while its composer has text,
      // so this only fires once they really stopped (or dropped off).
      if (typing) peerTypingRef.current = setTimeout(() => setPeerTyping(false), TYPING_EXPIRY_MS);
    });
    return () => {
      API.Realtime.leaveConversation(ticketId);
      unsub(); unsubTyping(); unsubReaction();
      if (peerTypingRef.current) clearTimeout(peerTypingRef.current);
    };
  }, [ticketId]);

  // Broadcast our own "typing" for as long as the composer holds text.
  const signalTyping = (text) => typingRef.current.update(ticketId, !!(text || '').trim());
  const stopTyping = () => typingRef.current.stop();

  // Le fil de cette réservation, créé si besoin — UN seul par réservation (le serveur refuse
  // le second par un 409, que l'API traite en relisant l'existant).
  const ensureTicket = async () => {
    if (ticketId) return ticketId;
    const ticket = await API.support.ensureBookingConversation(booking);
    if (!ticket || !ticket.id) throw new Error('Conversation indisponible');
    setTicketId(ticket.id);
    return ticket.id;
  };

  const send = async () => {
    if (!draft.trim() || !booking) return;
    const text = draft.trim();
    const quoted = replyTo;
    setDraft('');
    setReplyTo(null);
    stopTyping();
    setSending(true);
    try {
      const tid = await ensureTicket();
      const sent = await API.support.sendMessage(tid, text, quoted ? quoted.id : null);
      setMsgs(m => [...m, {
        id: sent ? sent.id : null,
        from: 'me', text,
        replyTo: sent ? sent.replyTo : (quoted ? { id: quoted.id, senderRole: quoted.from === 'me' ? 'lessor' : 'customer', preview: quoted.text } : null),
        reactions: [],
        t: t('asst.justNow'),
      }]);
    } catch (ex) {
      // Plus de faux « envoyé » : le message n'est PAS parti (réseau, clé de chiffrement
      // verrouillée après un rechargement…). On rend le texte au champ et on le dit.
      setDraft(text);
      toast({ variant: 'error', title: 'Envoi impossible', desc: ex.message });
    } finally {
      setSending(false);
    }
  };
  const onKey = (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); } };

  // Toggle a reaction. The server returns the message's whole summary, so we patch
  // just that message rather than reloading the thread.
  const react = async (msg, emoji) => {
    if (!msg.id) return;
    try {
      const res = await API.support.react(msg.id, emoji);
      setMsgs(prev => prev.map(m => m.id === msg.id ? { ...m, reactions: res.reactions || [] } : m));
    } catch (ex) {
      toast({ variant: 'error', title: t('asst.reactFailed'), desc: ex.message });
    }
  };

  const sendFile = async (file) => {
    if (!file || !booking) return;
    setSending(true);
    try {
      const tid = await ensureTicket();
      const msg = await API.support.sendAttachment(tid, file);
      setMsgs(m => [...m, { from: 'me', text: msg.body || '', attachment: msg.attachment || null, t: t('asst.justNow') }]);
    } catch (ex) {
      toast({ variant: 'error', title: 'Envoi impossible', desc: ex.message });
    } finally {
      setSending(false);
    }
  };

  // Several photos at once → ONE message (an album). The server returns the rows in album
  // order; appending them flat is enough because groupAlbums merges them at render.
  const sendFiles = async (files) => {
    if (!files || files.length === 0 || !booking) return;
    setSending(true);
    try {
      const tid = await ensureTicket();
      const rows = await API.support.sendAlbum(tid, files);
      const me = API.currentUserId();
      setMsgs(m => [...m, ...(rows || []).map(r => ({
        id: r.id, from: 'me', senderId: me, createdAt: r.createdAt,
        text: '', attachment: r.attachment || null, reactions: [], t: t('asst.justNow'),
      }))]);
    } catch (ex) {
      toast({ variant: 'error', title: 'Envoi impossible', desc: ex.message });
    } finally {
      setSending(false);
    }
  };

  const updateStatus = async (newStatus, reason) => {
    if (!booking) return;
    setActionLoading(newStatus);
    try {
      await API.lessor.updateBookingStatus(booking.id, newStatus, reason);
      setStatus(newStatus);
      toast({ variant: newStatus === 'confirmed' ? 'success' : 'info', title: newStatus === 'confirmed' ? t('book.confirmed') : newStatus === 'rejected' ? t('book.rejectedBooking') : t('det.statusUpdated'), desc: booking.ref });
    } catch (ex) {
      toast({ variant: 'error', title: t('common.error'), desc: ex.message });
    } finally {
      setActionLoading(null);
    }
  };

  // Approve/reject the renter's pending extension request. On approval the backend
  // re-checks availability, extends the reservation and bumps the amounts — we re-fetch.
  const respondExtension = async (approve) => {
    const ext = booking?.pendingExtension;
    if (!ext) return;
    setExtLoading(approve ? 'approve' : 'reject');
    try {
      await API.lessor.respondExtension(booking.id, ext.id, approve);
      const fresh = API.normalizeBooking(await API.lessor.getBooking(booking.id));
      setBooking(fresh);
      setStatus(fresh.status);
      toast({ variant: approve ? 'success' : 'info', title: approve ? t('det.extAccepted') : t('det.extRefused'), desc: booking.ref });
    } catch (ex) {
      toast({ variant: 'error', title: t('common.error'), desc: ex.message });
    } finally {
      setExtLoading(null);
    }
  };

  // ── Lifecycle guardrails ──────────────────────────────────────────────
  const _now = new Date();
  const _pickup = booking?.pickupAt ? new Date(booking.pickupAt) : null;
  const _pickupDay = _pickup ? new Date(_pickup.getFullYear(), _pickup.getMonth(), _pickup.getDate()) : null;
  const started = _pickup ? _now >= _pickup : false;                  // rental has begun (→ can complete)
  const canPay = _pickupDay ? _now >= _pickupDay : false;             // from day 1 of the rental (→ can cash in)
  const canRequestDeletion = !!booking
    && booking.rawPaymentStatus === 'not_paid'
    && (!_pickup || _now < _pickup)
    && !['completed', 'deleted', 'deletion_requested'].includes(status);
  const pickupLabel = _pickup ? _pickup.toLocaleDateString(loc, { day: 'numeric', month: 'long' }) : '';

  const openPayment = () => {
    if (!canPay) {
      toast({ variant: 'info', title: t('det.tooEarly'), desc: t('det.payNotYet', { when: pickupLabel ? ` (${pickupLabel})` : '' }) });
      return;
    }
    setPayingOpen(true);
  };
  const tryComplete = () => {
    if (!started) {
      toast({ variant: 'info', title: t('det.impossible'), desc: t('det.cannotFinish') });
      return;
    }
    updateStatus('completed');
  };
  const submitDeletion = async (reason) => {
    setDeleting(false);
    setDelLoading(true);
    try {
      await API.lessor.requestBookingDeletion(booking.id, reason);
      const fresh = API.normalizeBooking(await API.lessor.getBooking(booking.id));
      setBooking(fresh);
      setStatus(fresh.status);
      toast({ variant: 'success', title: t('det.requestSent'), desc: t('det.deletionPending') });
    } catch (ex) {
      toast({ variant: 'error', title: t('common.error'), desc: ex.message });
    } finally {
      setDelLoading(false);
    }
  };

  const cta = () => {
    // Payment button is greyed (muted look) until day 1; clicking early explains why.
    const payBtn = booking?.payment !== 'paid' && ['confirmed', 'completed', 'in_progress'].includes(status)
      ? <Button variant="secondary" icon={canPay ? 'banknote' : 'clock'} onClick={openPayment}
          title={canPay ? undefined : t('det.availableFrom', { date: pickupLabel })}
          style={canPay ? undefined : { opacity: .55 }}>{t('det.paymentReceived')}</Button>
      : null;
    if (status === 'deletion_requested') return <span className="muted" style={{ fontSize: 13 }}>{t('det.deletionAwaiting')}</span>;
    if (status === 'deleted') return <span className="muted" style={{ fontSize: 13 }}>{t('book.deleted')}</span>;
    if (status === 'pending') return <>
      {canRequestDeletion && <Button variant="ghost" icon="trash" loading={delLoading} onClick={() => setDeleting(true)}>{t('book.delete')}</Button>}
      <Button variant="danger" icon="x" loading={actionLoading === 'rejected'} onClick={() => setRejecting(true)}>{t('book.reject')}</Button>
      <Button variant="primary" icon="check" loading={actionLoading === 'confirmed'} onClick={() => updateStatus('confirmed')}>{t('det.confirmBooking')}</Button>
    </>;
    if (status === 'confirmed') return <>
      {canRequestDeletion && <Button variant="ghost" icon="trash" loading={delLoading} onClick={() => setDeleting(true)}>{t('book.delete')}</Button>}
      {payBtn}
      <Button variant="secondary" loading={actionLoading === 'cancelled'} onClick={() => updateStatus('cancelled')}>{t('book.cancel')}</Button>
      <Button variant="primary" icon="check" loading={actionLoading === 'completed'}
        onClick={tryComplete} title={started ? undefined : t('det.availableAfterStart')}
        style={started ? undefined : { opacity: .55 }}>{t('det.markCompleted')}</Button>
    </>;
    if (status === 'completed') return <>{payBtn}<Button variant="secondary" icon="download">{t('det.downloadContract')}</Button></>;
    return <Button variant="secondary" icon="download">{t('det.downloadContract')}</Button>;
  };

  if (loading) return (
    <div className="page-wide">
      <div className="card card-pad" style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
        <Sk w="30%" h={22} /><Sk w="100%" h={200} r={12} />
      </div>
    </div>
  );

  if (err || !booking) return (
    <div className="page-wide">
      <EmptyState variant="err" title={t('det.notFound')} action={<Button variant="secondary" onClick={() => navigate('bookings')}>{t('det.backToBookings')}</Button>} />
    </div>
  );

  const ref = booking.ref || refParam;
  const sub = booking.subtotal || booking.amount;

  // Libellés résolus ICI et non dans le `.map` plus bas : la variable de boucle s'y appelle
  // `t` et masquerait la fonction de traduction.
  const TABS = [
    { id: 'reservation', label: t('det.tabBooking'),   icon: 'calendar' },
    { id: 'locataire',   label: t('det.tabRenter'),    icon: 'user' },
    { id: 'paiement',    label: t('det.tabPayment'),   icon: 'wallet' },
    { id: 'documents',   label: t('det.tabDocuments'), icon: 'clipboard' },
  ];

  return (
    <div className="page-wide">
      <button className="btn btn-ghost btn-sm" style={{ marginBottom: 14, paddingLeft: 6 }} onClick={() => navigate('bookings')}>
        <Icon name="arrowLeft" size={16} /> Retour aux réservations
      </button>
      <div className="detail-head">
        <span className="dh-ref mono">{ref}</span>
        <StatusBadge map={KD.BOOKING_STATUS} value={status} />
        <StatusBadge map={KD.PAYMENT_STATUS} value={booking.payment} />
        <div className="spacer" style={{ flex: 1 }} />
        <div className="row" style={{ gap: 10 }}>{cta()}</div>
      </div>

      {(status === 'deletion_requested' || status === 'deleted') && (
        <div className="card card-pad" style={{ marginBottom: 16, borderColor: status === 'deleted' ? 'var(--line)' : 'var(--amber-300, #fcd34d)', display: 'flex', alignItems: 'center', gap: 12 }}>
          <Icon name={status === 'deleted' ? 'trash' : 'clock'} size={20} className="muted" />
          <div style={{ fontSize: 13.5 }}>
            {status === 'deleted'
              ? t('det.deletedNote')
              : t('det.deletionRequested')}
            {booking.deletionReason && <span className="muted"> · Motif : {booking.deletionReason}</span>}
          </div>
        </div>
      )}

      <div className="detail-grid">
        {/* LEFT — scrollable avec tabs */}
        <div style={{ display: 'flex', flexDirection: 'column', gap: 0, minHeight: 0 }}>
          {/* Tab navigation */}
          <div className="seg" style={{ marginBottom: 16 }}>
            {TABS.map(t => (
              <button key={t.id} className={activeTab === t.id ? 'on' : ''}
                onClick={() => setActiveTab(t.id)}
                style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                <Icon name={t.icon} size={14} />{t.label}
              </button>
            ))}
          </div>

          {/* Tab: Réservation */}
          {activeTab === 'reservation' && (
            <div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
              {booking.pendingExtension && (
                <Card style={{ borderColor: 'var(--amber-300, #fcd34d)' }}>
                  <CardHead title={t('det.extensionRequest')} icon={{ name: 'clock', color: 'amber' }} />
                  <div className="card-pad">
                    <div className="row" style={{ gap: 22, flexWrap: 'wrap' }}>
                      <div>
                        <div className="muted" style={{ fontSize: 12.5, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '.4px' }}>{t('det.extraDays')}</div>
                        <div style={{ fontWeight: 800, fontSize: 16, marginTop: 5 }}>+{booking.pendingExtension.extraDays} j</div>
                      </div>
                      <div>
                        <div className="muted" style={{ fontSize: 12.5, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '.4px' }}>{t('det.newReturn')}</div>
                        <div style={{ fontWeight: 800, fontSize: 16, marginTop: 5 }}>
                          {booking.pendingExtension.newReturnAt ? new Date(booking.pendingExtension.newReturnAt).toLocaleString('fr-FR', { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' }) : '—'}
                        </div>
                      </div>
                      <div>
                        <div className="muted" style={{ fontSize: 12.5, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '.4px' }}>{t('det.cost')}</div>
                        <div style={{ fontWeight: 800, fontSize: 16, marginTop: 5 }}>{KD.fmtDA(parseFloat(booking.pendingExtension.extraAmount || 0))}</div>
                      </div>
                    </div>
                    <div className="row" style={{ gap: 10, marginTop: 16 }}>
                      <Button variant="danger" icon="x" loading={extLoading === 'reject'} onClick={() => respondExtension(false)}>{t('det.refuse')}</Button>
                      <Button variant="primary" icon="check" loading={extLoading === 'approve'} onClick={() => respondExtension(true)}>{t('det.acceptExtension')}</Button>
                    </div>
                  </div>
                </Card>
              )}
              <Card>
                <CardHead title={t('det.rentalDetails')} icon={{ name: 'calendar', color: 'blue' }} />
                <div className="card-pad">
                  <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 18 }}>
                    <div>
                      <div className="muted" style={{ fontSize: 12.5, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '.4px' }}>{t('det.pickup')}</div>
                      <div style={{ fontWeight: 700, fontSize: 15, marginTop: 6 }}>{booking.start}</div>
                      {booking.pickupLocation && <div className="muted" style={{ fontSize: 13.5, marginTop: 3, display: 'flex', alignItems: 'center', gap: 5 }}><Icon name="pin" size={14} />{booking.pickupLocation}</div>}
                    </div>
                    <div>
                      <div className="muted" style={{ fontSize: 12.5, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '.4px' }}>{t('det.return')}</div>
                      <div style={{ fontWeight: 700, fontSize: 15, marginTop: 6 }}>{booking.end}</div>
                      {booking.returnLocation && <div className="muted" style={{ fontSize: 13.5, marginTop: 3, display: 'flex', alignItems: 'center', gap: 5 }}><Icon name="pin" size={14} />{booking.returnLocation}</div>}
                    </div>
                  </div>
                  <div style={{ display: 'flex', gap: 18, marginTop: 18, paddingTop: 16, borderTop: '1px solid var(--line)' }}>
                    <div className="row" style={{ gap: 7 }}><Icon name="clock" size={16} className="muted" /><span style={{ fontWeight: 700 }}>{booking.days} jours</span></div>
                    {booking.createdAt && (
                      <div className="row" style={{ gap: 7 }}>
                        <Icon name="calendar" size={16} className="muted" />
                        <span className="muted">Réservée le {new Date(booking.createdAt).toLocaleString('fr-FR', { day: 'numeric', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' })}</span>
                      </div>
                    )}
                  </div>
                  {booking.notes && (
                    <div style={{ marginTop: 16, background: 'var(--slate-50)', borderRadius: 12, padding: '13px 15px' }}>
                      <div className="muted" style={{ fontSize: 12.5, fontWeight: 700 }}>{t('det.customerNote')}</div>
                      <div style={{ fontSize: 14, marginTop: 5, lineHeight: 1.5 }}>« {booking.notes} »</div>
                    </div>
                  )}
                  {(booking.pickupLat != null || booking.returnLat != null) && (
                    <div style={{ marginTop: 16 }}>
                      <div className="muted" style={{ fontSize: 12.5, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '.4px', marginBottom: 8 }}>Localisation</div>
                      <MapView height={220} points={[
                        booking.pickupLat != null ? { lat: booking.pickupLat, lng: booking.pickupLng, label: 'Prise en charge' + (booking.pickupLocation ? ' — ' + booking.pickupLocation : ''), color: '#059669' } : null,
                        booking.returnLat != null ? { lat: booking.returnLat, lng: booking.returnLng, label: 'Retour' + (booking.returnLocation ? ' — ' + booking.returnLocation : ''), color: '#2563eb' } : null,
                      ].filter(Boolean)} />
                      <div className="row" style={{ gap: 16, marginTop: 8, fontSize: 12, color: 'var(--muted)' }}>
                        {booking.pickupLat != null && <span className="row" style={{ gap: 5 }}><span style={{ width: 10, height: 10, borderRadius: 99, background: '#059669', display: 'inline-block' }} />Prise en charge</span>}
                        {booking.returnLat != null && <span className="row" style={{ gap: 5 }}><span style={{ width: 10, height: 10, borderRadius: 99, background: '#2563eb', display: 'inline-block' }} />{t('det.return')}</span>}
                      </div>
                    </div>
                  )}
                </div>
              </Card>

              {booking.vehicle && (
                <Card>
                  <CardHead title={t('det.vehicle')} icon={{ name: 'car', color: 'green' }}
                    right={<Button size="sm" variant="ghost" iconRight="arrowUpRight" onClick={() => navigate('vehicles')}>{t('det.seeListing')}</Button>} />
                  <div className="card-pad" style={{ display: 'flex', gap: 16 }}>
                    <VehiclePhoto vehicle={API.normalizeVehicle(booking.vehicle)} w={150} h={104} radius={14} />
                    <div style={{ flex: 1 }}>
                      <div style={{ fontWeight: 800, fontSize: 17 }}>{booking.vehicleName}</div>
                      <div className="muted" style={{ fontSize: 13.5, marginTop: 2 }}>{booking.vehicle.category} · {booking.vehicle.pickupCity}</div>
                      <div style={{ display: 'flex', gap: 16, marginTop: 12 }}>
                        <span className="vspec"><Icon name="seats" size={15} />{booking.vehicle.seats} places</span>
                        <span className="vspec"><Icon name="fuel" size={15} />{booking.vehicle.fuelType}</span>
                      </div>
                      <div style={{ marginTop: 12, fontWeight: 800, fontSize: 16 }}>{KD.fmtDA(parseFloat(booking.vehicle.dailyPriceBase))} <span className="muted" style={{ fontSize: 13, fontWeight: 600 }}>/ jour</span></div>
                    </div>
                  </div>
                </Card>
              )}

              {inspections.length > 0 && (
                <Card>
                  <CardHead title={t('det.inspection')} icon={{ name: 'image', color: 'blue' }} />
                  <div className="card-pad" style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
                    {inspections.map(insp => (
                      <div key={insp.id}>
                        <div style={{ fontWeight: 700, fontSize: 13.5, marginBottom: 8 }}>
                          {insp.mode === 'checkin' ? t('det.checkIn') : t('det.checkOut')}
                          {/* Date + HEURE, et l'année : « 7 oct. » ne dit pas si le dépôt
                              précède ou suit la remise des clés, alors que c'est toute la
                              valeur probante de l'horodatage en cas de litige. */}
                          {insp.createdAt ? ' · ' + new Date(insp.createdAt).toLocaleString('fr-FR', { day: 'numeric', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' }) : ''}
                          {/* Un dépôt tardif est accepté depuis 0.9.10 — encore faut-il que
                              le loueur le VOIE. `submittedLate` était renvoyé par l'API et lu
                              nulle part. */}
                          {insp.submittedLate && (
                            <span className="pill pill-amber" style={{ marginLeft: 8, fontSize: 11 }}>
                              {t('det.inspectionLate')}
                            </span>
                          )}
                        </div>
                        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(110px, 1fr))', gap: 8 }}>
                          {(insp.photos || []).map(p => (
                            <AuthedImg key={p.id} path={p.downloadPath} alt={p.position}
                              style={{ width: '100%', height: 80, objectFit: 'cover', borderRadius: 8, border: '1px solid var(--line)', display: 'block' }} />
                          ))}
                        </div>
                        {insp.notes && <div className="muted" style={{ fontSize: 13, marginTop: 8, lineHeight: 1.5 }}>« {insp.notes} »</div>}
                      </div>
                    ))}
                  </div>
                </Card>
              )}
            </div>
          )}

          {/* Tab: Locataire */}
          {activeTab === 'locataire' && (
            <Card>
              <CardHead title="Locataire" icon={{ name: 'user', color: 'violet' }} />
              <div className="card-pad">
                <div className="celluser" style={{ marginBottom: 16 }}>
                  <Avatar name={booking.renter?.name || '?'} color={booking.renter?.color} src={booking.renter?.avatarUrl} size={52} />
                  <div className="cu-meta">
                    <div className="n" style={{ fontSize: 16 }}>{booking.renter?.name}</div>
                    {booking.renter?.phone && <div className="e">{booking.renter.phone}</div>}
                    {booking.renter?.phoneVerified === false && (
                      <div className="e" title={t('det.phoneUnverifiedHint')} style={{ color: 'var(--amber-700, #b45309)', fontWeight: 600 }}>{t('det.phoneUnverified')}</div>
                    )}
                  </div>
                </div>
                <Button variant="secondary" size="sm" icon="message" onClick={focusComposer}>{t('det.sendMessage')}</Button>
              </div>
            </Card>
          )}

          {/* Tab: Paiement */}
          {activeTab === 'paiement' && (
            <Card>
              <CardHead title="Paiements" icon={{ name: 'wallet', color: 'amber' }} />
              <div className="card-pad">
                {(() => {
                  // Tous les montants viennent du serveur (GET /bookings/:id → accounting).
                  // Aucun calcul monétaire ici : c'est ce qui garantit que cet écran, la page
                  // Revenus, la facture PDF et la finance admin donnent le même chiffre.
                  const rate = booking.commissionRate != null ? booking.commissionRate : 0.1;
                  const a = booking.accounting || {
                    // Repli sur les champs bruts si le détail n'a pas encore été rechargé.
                    rental: (sub || 0) + (booking.extra || 0),
                    serviceFee: booking.serviceFee || 0,
                    discount: booking.discount || 0,
                    clientTotal: booking.amount || 0,
                    deposit: booking.deposit || 0,
                    commission: Math.round(((sub || 0) + (booking.extra || 0)) * rate),
                    lessorNet: Math.round(((sub || 0) + (booking.extra || 0)) * (1 - rate)),
                    netDueToPlatform: Math.round(((sub || 0) + (booking.extra || 0)) * rate) + (booking.serviceFee || 0) - (booking.discount || 0),
                    appliedCode: booking.promoCode,
                  };
                  const owesLessor = a.netDueToPlatform < 0;
                  return (
                    <>
                      {/* Ce que règle le client */}
                      <div className="kv kv-money"><span className="k">{t('det.rentalDays', { n: booking.days })}</span><span className="v">{KD.fmtDA(a.rental)}</span></div>
                      <div className="kv kv-money"><span className="k">{t('det.serviceFee')} <span className="muted" style={{ fontSize: 12 }}>{t('det.paidByCustomer')}</span></span><span className="v">{KD.fmtDA(a.serviceFee)}</span></div>
                      {a.discount > 0 && (
                        <div className="kv kv-money">
                          <span className="k">{t('det.discount')}{a.appliedCode ? ` · ${a.appliedCode}` : ''} <Badge cls="blue">Uleez</Badge></span>
                          <span className="v" style={{ color: 'var(--brand-600)' }}>- {KD.fmtDA(a.discount)}</span>
                        </div>
                      )}
                      <div className="kv kv-money" style={{ borderTop: '1px solid var(--line)', marginTop: 4 }}>
                        <span className="k" style={{ fontWeight: 700, color: 'var(--ink)' }}>{t('det.totalPaidByCustomer')}</span>
                        <span className="v">{KD.fmtDA(a.clientTotal)}</span>
                      </div>
                      <div className="kv kv-money"><span className="k">{t('det.deposit')} <span className="muted" style={{ fontSize: 12 }}>{t('det.depositNote')}</span></span><span className="v">{KD.fmtDA(a.deposit)}</span></div>

                      {/* Votre décompte */}
                      <div style={{ marginTop: 16, marginBottom: 2, fontSize: 12, fontWeight: 800, textTransform: 'uppercase', letterSpacing: '.4px', color: 'var(--muted)' }}>{t('det.yourBreakdown')}</div>
                      <div className="kv kv-money"><span className="k">{t('det.rental')}</span><span className="v">{KD.fmtDA(a.rental)}</span></div>
                      <div className="kv kv-money">
                        <span className="k">
                          {t('rev.commissionUleez', { pct: (rate * 100).toFixed(1) })}
                          {booking.isWelcomePeriod && <Badge cls="green">{t('det.welcome')}</Badge>}
                        </span>
                        <span className="v" style={{ color: 'var(--rose-600)' }}>- {KD.fmtDA(a.commission)}</span>
                      </div>
                      <div className="kv kv-money" style={{ borderTop: '1px solid var(--line)', marginTop: 4 }}>
                        <span className="k" style={{ fontWeight: 800, color: 'var(--ink)' }}>{t('det.totalReceived')}</span>
                        <span className="v" style={{ fontSize: 16 }}>{KD.fmtDA(a.lessorNet)}</span>
                      </div>

                      {/* Solde avec Uleez. La remise est financée par Uleez : elle réduit ce que
                          le loueur reverse, sinon il l'aurait absorbée sans le savoir. */}
                      <div style={{ marginTop: 14, padding: '12px 14px', background: 'var(--surface-2, #f8fafc)', borderRadius: 10, border: '1px solid var(--line)' }}>
                        <div style={{ fontSize: 12.5, fontWeight: 800, color: 'var(--ink)', marginBottom: 8 }}>
                          {owesLessor ? t('det.uleezRefunds') : t('det.toRemit')}
                        </div>
                        <div className="kv kv-money" style={{ padding: '3px 0' }}><span className="k">{t('det.commission')}</span><span className="v">{KD.fmtDA(a.commission)}</span></div>
                        <div className="kv kv-money" style={{ padding: '3px 0' }}><span className="k">{t('det.feesCollected')}</span><span className="v">{KD.fmtDA(a.serviceFee)}</span></div>
                        {a.discount > 0 && (
                          <div className="kv kv-money" style={{ padding: '3px 0' }}>
                            <span className="k">{t('det.discountFunded', { code: a.appliedCode ? ` ${a.appliedCode}` : '' })}</span>
                            <span className="v" style={{ color: 'var(--brand-600)' }}>- {KD.fmtDA(a.discount)}</span>
                          </div>
                        )}
                        <div className="kv kv-money" style={{ padding: '3px 0', borderTop: '1px solid var(--line)', marginTop: 2 }}>
                          <span className="k" style={{ fontWeight: 800, color: 'var(--ink)' }}>
                            {owesLessor ? t('det.totalToReceive') : t('det.totalToRemit')}
                          </span>
                          <span className="v" style={{ fontWeight: 800 }}>{KD.fmtDA(Math.abs(a.netDueToPlatform))}</span>
                        </div>
                        <div className="muted" style={{ fontSize: 11.5, marginTop: 7, lineHeight: 1.45 }}>
                          {t('det.balanceCheck', {
                            total: KD.fmtDA(a.clientTotal),
                            sign: owesLessor ? '+' : '−',
                            due: KD.fmtDA(Math.abs(a.netDueToPlatform)),
                            net: KD.fmtDA(a.lessorNet),
                          })}
                        </div>
                      </div>
                    </>
                  );
                })()}
                <div style={{ marginTop: 14 }}>
                  <StatusBadge map={KD.PAYMENT_STATUS} value={booking.payment} />
                </div>
                {booking.payment !== 'paid' && ['confirmed', 'completed', 'in_progress'].includes(status) && (
                  <div style={{ marginTop: 14 }}>
                    <Button variant="primary" icon={canPay ? 'banknote' : 'clock'} onClick={openPayment}
                      title={canPay ? undefined : `Disponible à partir du ${pickupLabel}`}
                      style={canPay ? undefined : { opacity: .55 }}>{t('pay.recordTitle')}</Button>
                    {!canPay && <div className="muted" style={{ fontSize: 12.5, marginTop: 6 }}>{t('det.payFromDay1', { date: pickupLabel })}</div>}
                  </div>
                )}
              </div>
            </Card>
          )}

          {/* Tab: Documents */}
          {activeTab === 'documents' && (
            <Card>
              <CardHead title={t('det.renterDocs')} icon={{ name: 'clipboard', color: 'blue' }} />
              <div className="card-pad">
                {documents.length === 0
                  ? <EmptyState variant="empty" icon="clipboard" title={t('det.noDocs')} desc={t('det.noDocsDesc')} />
                  : (
                    <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))', gap: 12 }}>
                      {documents.map(d => {
                        // Encrypted at rest, fetched through the authenticated content
                        // endpoint — these are the renter's ID card and driving licence,
                        // and they used to hang off an unauthenticated /uploads URL.
                        const typeLabel = (API.DOCUMENT_TYPES.find(t => t.value === d.documentType) || {}).label || d.documentType;
                        const isPdf = (d.mimeType || '') === 'application/pdf';
                        return (
                          <div key={d.id}>
                            <div className="row" style={{ justifyContent: 'space-between', marginBottom: 6 }}>
                              <span style={{ fontWeight: 700, fontSize: 13 }}>{typeLabel}</span>
                              {/* No file ⇒ awaiting resubmission, not refused. The v0.7.4
                                  migration set 'rejected' on every pre-encryption row; a red
                                  badge there reads as "this renter failed KYC", which is wrong. */}
                              <StatusBadge map={KD.DOCUMENT_STATUS} value={d.hasContent ? d.verificationStatus : 'pending'} />
                            </div>
                            {!d.hasContent
                              ? <div className="muted" style={{ fontSize: 12.5, padding: '14px', borderRadius: 10, border: '1px dashed var(--line)' }}>
                                  {t('det.docPending')}
                                </div>
                              : isPdf
                                ? <div className="row" onClick={() => API.docs.download(d, `${d.documentType}.pdf`).catch(() => toast({ variant: 'error', title: t('det.downloadFailed') }))}
                                       style={{ gap: 10, padding: '18px 14px', color: 'var(--ink)', cursor: 'pointer', borderRadius: 10, border: '1px solid var(--line)', background: 'var(--slate-100)' }}>
                                    <Icon name="clipboard" size={22} className="muted" />
                                    <div><div style={{ fontWeight: 700, fontSize: 13 }}>{t('det.encryptedPdf')}</div><div className="muted" style={{ fontSize: 12 }}>{t('det.clickToDownload')}</div></div>
                                  </div>
                                : <div style={{ borderRadius: 10, overflow: 'hidden', border: '1px solid var(--line)', background: 'var(--slate-100)' }}>
                                    <AuthedImg path={d} alt={typeLabel} fetcher={API.docs.objectUrl}
                                               style={{ width: '100%', height: 150, objectFit: 'cover', display: 'block' }} />
                                  </div>}
                          </div>
                        );
                      })}
                    </div>
                  )}
              </div>
            </Card>
          )}
        </div>

        {/* RIGHT — messaging panel (sticky) */}
        <Card style={{ display: 'flex', flexDirection: 'column', height: 'calc(100vh - var(--header-h) - 130px)', minHeight: 480, position: 'sticky', top: 'calc(var(--header-h) + 20px)' }}>
          <div className="thread-head">
            <Avatar name={booking.renter?.name || '?'} color={booking.renter?.color} src={booking.renter?.avatarUrl} size={40} />
            <div className="th-meta">
              <div className="n">{booking.renter?.name}</div>
              <div className="r"><Icon name="clipboard" size={12} />{ref}</div>
            </div>
            <div className="spacer" style={{ flex: 1 }} />
            <IconButton name="phone" size="sm" />
          </div>
          <div className="thread-body" ref={bodyRef}>
            {msgs.length === 0 && !peerTyping
              ? <EmptyState variant="empty" icon="message" title={t('det.noMessage')} desc={t('det.noMessageDesc')} />
              : <>
                <div className="day-sep">{t('det.conversation')}</div>
                {groupAlbums(msgs).map((m, i) => (
                  <ChatBubble
                    key={m.id || i}
                    msg={m}
                    quotedSource={m.replyTo ? msgs.find((x) => x.id === m.replyTo.id) || null : null}
                    onReply={setReplyTo}
                    onReact={react}
                  />
                ))}
                {peerTyping ? <TypingBubble /> : null}
              </>}
          </div>
          {replyTo && (
            <div className="reply-banner">
              <div style={{ flex: 1, minWidth: 0 }}>
                <div className="rb-author">{replyTo.from === 'me' ? t('chat.quoteYou') : t('chat.quoteClient')}</div>
                {/* Même aperçu que dans la bulle citée : une photo, un vocal ou une position
                    se reconnaissent, au lieu du « 📍 Position » que ce repli affichait pour
                    TOUT message sans texte — vocal et pièce jointe compris. */}
                <div className="rb-text">
                  {(() => {
                    const q = quotePreview(t, null, replyTo);
                    return <>
                      {q.icon ? <Icon name={q.icon} size={13} className="bq-icon" /> : null}
                      <span>{q.text}</span>
                    </>;
                  })()}
                </div>
              </div>
              <button className="rb-close" onClick={() => setReplyTo(null)} title={t('asst.cancelReply')}>
                <Icon name="x" size={15} />
              </button>
            </div>
          )}
          <div className="thread-input">
            <label className="btn btn-secondary btn-icon sm" style={{ cursor: 'pointer', margin: 0 }} title={t('asst.attachHint')}>
              <Icon name="image" size={18} />
              <input type="file" multiple accept="image/jpeg,image/png,image/webp,application/pdf" style={{ display: 'none' }}
                onChange={(e) => {
                  // One file keeps the single-attachment path (so a lone photo still has a
                  // NULL group and renders exactly as before); several become an album.
                  const fs = Array.from(e.target.files || []);
                  e.target.value = '';
                  if (fs.length === 1) sendFile(fs[0]);
                  else if (fs.length > 1) sendFiles(fs);
                }} />
            </label>
            <div className="ti-box" ref={composerRef}>
              <Textarea placeholder={t('asst.messagePlaceholder')} value={draft} onChange={(e) => { setDraft(e.target.value); signalTyping(e.target.value); }} onKeyDown={onKey} style={{ minHeight: 44, maxHeight: 110 }} />
            </div>
            <Button variant="primary" className="btn-icon" loading={sending} onClick={send} style={{ height: 44, width: 44 }}>
              {!sending && <Icon name="send" size={18} />}
            </Button>
          </div>
        </Card>
      </div>

      {rejecting && (
        <RejectModal
          title={t('book.rejectBooking')}
          who={`${booking.renter?.name} · ${ref}`}
          onClose={() => setRejecting(false)}
          onConfirm={(reason) => { updateStatus('rejected', reason); setRejecting(false); }}
        />
      )}
      {payingOpen && (
        <RecordPaymentModal
          booking={booking}
          onClose={() => setPayingOpen(false)}
          onSuccess={() => {
            setBooking(b => ({ ...b, payment: 'paid' }));
            setPayingOpen(false);
          }}
        />
      )}
      {deleting && (
        <RejectModal
          title={t('det.requestDeletion')}
          who={`${booking.renter?.name} · ${ref}`}
          onClose={() => setDeleting(false)}
          onConfirm={submitDeletion}
        />
      )}
    </div>
  );
}

// ============================================================
// CALENDAR / PLANNING
// ============================================================
function LessorCalendar({ currentUser }) {
  const t = window.I18N ? I18N.useT() : ((k) => k);
  const toast = useToast();
  const [vehicles, setVehicles] = useS2([]);
  const [selectedVehicleId, setSelectedVehicleId] = useS2('all');
  const [bookings, setBookings] = useS2([]);
  const [availabilities, setAvailabilities] = useS2([]); // manual blocks, all vehicles
  const [selDay, setSelDay] = useS2(null);
  const [loading, setLoading] = useS2(true);
  const [blocking, setBlocking] = useS2(false);
  const [blockForm, setBlockForm] = useS2({ vehicleId: '', startAt: '', endAt: '', reason: '' });
  const [blockLoading, setBlockLoading] = useS2(false);

  const now = new Date();
  const [year, setYear] = useS2(now.getFullYear());
  const [month, setMonth] = useS2(now.getMonth()); // 0-indexed

  const load = () => {
    setLoading(true);
    API.lessor.getVehicles()
      .then(async data => {
        const list = Array.isArray(data) ? data.map(API.normalizeVehicle) : [];
        setVehicles(list);
        if (list.length > 0) setBlockForm(f => ({ ...f, vehicleId: f.vehicleId || list[0].id }));

        const [bookingsData, availLists] = await Promise.all([
          API.lessor.getBookings().catch(() => []),
          Promise.all(list.map(v => API.lessor.getAvailabilities(v.id).catch(() => []))),
        ]);

        setBookings(Array.isArray(bookingsData) ? bookingsData.map(API.normalizeBooking) : []);

        const blocks = [];
        list.forEach((v, i) => {
          (availLists[i] || []).forEach(a => {
            if (a.source === 'manual_block') blocks.push({ ...a, vehicleId: v.id, vehicleName: `${v.brand} ${v.model}` });
          });
        });
        setAvailabilities(blocks);
        setLoading(false);
      })
      .catch(() => setLoading(false));
  };

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

  const prevMonth = () => { if (month === 0) { setMonth(11); setYear(y => y - 1); } else setMonth(m => m - 1); };
  const nextMonth = () => { if (month === 11) { setMonth(0); setYear(y => y + 1); } else setMonth(m => m + 1); };

  // Build calendar grid
  const firstDay = new Date(year, month, 1).getDay(); // 0 = Sunday
  const startOffset = firstDay === 0 ? 6 : firstDay - 1; // Convert to Mon-first
  const daysInMonth = new Date(year, month + 1, 0).getDate();
  const cells = [];
  for (let i = 0; i < startOffset; i++) cells.push({ out: true });
  for (let d = 1; d <= daysInMonth; d++) cells.push({ day: d });
  while (cells.length % 7 !== 0) cells.push({ out: true });

  // All "occupied" items: active bookings + manual blocks, normalized to a common shape.
  // Compare using plain YYYY-MM-DD date strings (no Date()/timezone math) to avoid off-by-one shifts.
  const dateOnly = (iso) => (iso || '').slice(0, 10);
  const pad = (n) => String(n).padStart(2, '0');
  const cellDate = (day) => `${year}-${pad(month + 1)}-${pad(day)}`;

  const items = [
    ...bookings
      .filter(b => ['pending', 'confirmed', 'in_progress', 'completed'].includes(b.status) && b.pickupAt && b.returnAt)
      .map(b => ({
        type: b.status === 'pending' ? 'pending' : 'confirmed',
        vehicleId: b.vehicleId,
        vehicleName: b.vehicleName,
        label: b.renter?.name || 'Locataire',
        renterName: b.renter?.name,
        ref: b.ref,
        amount: b.amount,
        status: b.status,
        start: dateOnly(b.pickupAt),
        end: dateOnly(b.returnAt),
        startLabel: b.start,
        endLabel: b.end,
      })),
    ...availabilities.map(a => ({
      type: 'blocked',
      vehicleId: a.vehicleId,
      vehicleName: a.vehicleName,
      label: t('cal.blocked'),
      start: dateOnly(a.startAt),
      end: dateOnly(a.endAt),
      startLabel: new Date(a.startAt).toLocaleDateString('fr-FR', { day: 'numeric', month: 'short' }),
      endLabel: new Date(a.endAt).toLocaleDateString('fr-FR', { day: 'numeric', month: 'short' }),
      id: a.id,
    })),
  ];

  const itemsForDay = (day) => {
    const ds = cellDate(day);
    return items.filter(it => (selectedVehicleId === 'all' || it.vehicleId === selectedVehicleId) && ds >= it.start && ds <= it.end);
  };

  const monthName = new Date(year, month, 1).toLocaleDateString('fr-FR', { month: 'long', year: 'numeric' });
  const DOW = ['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim'];
  const todayDay = now.getFullYear() === year && now.getMonth() === month ? now.getDate() : -1;
  const dayEvents = selDay ? itemsForDay(selDay) : [];

  const doBlock = async () => {
    if (!blockForm.vehicleId || !blockForm.startAt || !blockForm.endAt) return;
    setBlockLoading(true);
    try {
      await API.lessor.blockDates(blockForm.vehicleId, blockForm.startAt, blockForm.endAt, blockForm.reason);
      load();
      toast({ variant: 'success', title: t('cal.blockedDates'), desc: t('cal.unavailable') });
      setBlocking(false);
    } catch (ex) {
      toast({ variant: 'error', title: t('common.error'), desc: ex.message });
    } finally {
      setBlockLoading(false);
    }
  };

  return (
    <div className="page-wide">
      <div className="toolbar">
        <div className="row" style={{ gap: 8 }}>
          <IconButton name="chevLeft" onClick={prevMonth} />
          <h2 style={{ margin: 0, fontSize: 20, fontWeight: 800, minWidth: 200, textAlign: 'center', textTransform: 'capitalize' }}>{monthName}</h2>
          <IconButton name="chevRight" onClick={nextMonth} />
          <Button variant="secondary" size="sm" onClick={() => { setYear(now.getFullYear()); setMonth(now.getMonth()); }}>Aujourd'hui</Button>
        </div>
        <div className="spacer" />
        <Select value={selectedVehicleId} onChange={(e) => setSelectedVehicleId(e.target.value)}>
          <option value="all">{t('cal.allVehicles')}</option>
          {vehicles.map(v => <option key={v.id} value={v.id}>{v.brand} {v.model}</option>)}
        </Select>
        <Button variant="secondary" icon="refresh" size="sm" onClick={load}>{t('common.refresh')}</Button>
        <Button variant="primary" icon="plus" onClick={() => setBlocking(true)}>{t('cal.blockDates')}</Button>
      </div>

      {loading ? (
        <Card className="card-pad"><Sk w="100%" h={420} r={12} /></Card>
      ) : (
      <Card className="card-pad">
        <div className="between" style={{ marginBottom: 16 }}>
          <div className="cal-legend">
            <div className="li"><span className="sw" style={{ background: 'var(--brand-50)', borderLeft: '3px solid #059669' }} />{t('cal.confirmedOngoing')}</div>
            <div className="li"><span className="sw" style={{ background: 'var(--amber-100)', borderLeft: '3px solid #d97706' }} />{t('book.tabPending')}</div>
            <div className="li"><span className="sw" style={{ background: 'var(--slate-200)', borderLeft: '3px solid var(--slate-500)' }} />{t('cal.blocked')}</div>
          </div>
          <span className="muted" style={{ fontSize: 13 }}>{t('cal.clickDay')}</span>
        </div>
        <div className="cal-grid">
          {DOW.map(d => <div key={d} className="cal-dow">{d}</div>)}
          {cells.map((c, i) => {
            const dayItems = c.out ? [] : itemsForDay(c.day);
            const visible = dayItems.slice(0, 3);
            const extra = dayItems.length - visible.length;
            return (
              <div key={i} className={`cal-cell ${c.out ? 'out' : ''} ${c.day === todayDay ? 'today' : ''}`}
                onClick={() => !c.out && setSelDay(c.day)}>
                <span className="cal-num">{c.out ? '' : c.day}</span>
                {visible.map((ev, j) => (
                  <div key={j} className={`cal-ev ${ev.type}`}>
                    {selectedVehicleId === 'all' ? `${ev.vehicleName} · ${ev.label}` : ev.label}
                  </div>
                ))}
                {extra > 0 && <div className="cal-ev more">+{extra}</div>}
              </div>
            );
          })}
        </div>
      </Card>
      )}

      {!loading && (
      <Card style={{ marginTop: 16 }}>
        <CardHead title={t('cal.fleetOverview')} icon={{ name: 'car', color: 'blue' }}
          desc={t('cal.blockedDays', { month: monthName })} />
        <div className="card-pad" style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
          {vehicles.length === 0
            ? <EmptyState variant="empty" icon="car" title={t('cal.noVehicle')} />
            : vehicles.map(v => {
                const vItems = items.filter(it => it.vehicleId === v.id);
                return (
                  <div key={v.id}>
                    <div className="row" style={{ gap: 8, marginBottom: 6 }}>
                      <span style={{ fontWeight: 700, fontSize: 13.5 }}>{v.brand} {v.model}</span>
                      <span className="muted" style={{ fontSize: 12.5 }}>{v.plate}</span>
                    </div>
                    <div className="cal-mini">
                      {Array.from({ length: daysInMonth }, (_, i) => i + 1).map(day => {
                        const ds = cellDate(day);
                        const dayIt = vItems.filter(it => ds >= it.start && ds <= it.end);
                        const cls = dayIt.some(it => it.type === 'blocked') ? 'blocked'
                          : dayIt.some(it => it.type === 'confirmed') ? 'confirmed'
                          : dayIt.some(it => it.type === 'pending') ? 'pending' : '';
                        return (
                          <div key={day} className={`cal-mini-cell ${cls} ${day === todayDay ? 'today' : ''}`}
                            title={dayIt.length ? `${day}: ${dayIt.map(it => it.label).join(', ')}` : `${day}`}
                            onClick={() => { setSelectedVehicleId(v.id); setSelDay(day); }}>
                            {day}
                          </div>
                        );
                      })}
                    </div>
                  </div>
                );
              })}
        </div>
      </Card>
      )}

      {selDay && (
        <Modal
          title={`${selDay} ${new Date(year, month, selDay).toLocaleDateString('fr-FR', { month: 'long', year: 'numeric' })}`}
          desc={dayEvents.length ? t('cal.eventCount', { n: dayEvents.length }) : t('cal.noEvent')}
          icon={{ name: 'calendar', color: 'blue' }}
          onClose={() => setSelDay(null)}>
          {dayEvents.length === 0
            ? <EmptyState variant="empty" icon="calendar" title={t('cal.freeDay')} desc={t('cal.freeDayDesc')} />
            : <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
                {dayEvents.map((ev, i) => (
                  <div key={i} className="req" style={{ padding: 14 }}>
                    <div className={`sc-ico ${ev.type === 'blocked' ? 'rose' : ev.type === 'pending' ? 'amber' : 'green'}`} style={{ width: 40, height: 40, borderRadius: 11 }}>
                      <Icon name={ev.type === 'blocked' ? 'ban' : 'car'} size={19} />
                    </div>
                    <div className="req-body">
                      <div className="req-top">
                        <span className="n">{ev.type === 'blocked' ? t('cal.blocked') : ev.renterName}</span>
                        {ev.type !== 'blocked' && <Badge cls={ev.type === 'pending' ? 'amber' : 'green'}>{KD.BOOKING_STATUS[ev.status] ? t(KD.BOOKING_STATUS[ev.status].label) : ev.status}</Badge>}
                      </div>
                      <div className="req-veh">{ev.vehicleName}</div>
                      <div className="muted" style={{ fontSize: 12.5, marginTop: 2 }}>
                        {ev.startLabel} → {ev.endLabel}{ev.ref ? ` · ${ev.ref}` : ''}
                      </div>
                      {ev.amount != null && <div style={{ fontWeight: 700, marginTop: 4 }}>{KD.fmtDA(ev.amount)}</div>}
                    </div>
                    {ev.type === 'blocked' && ev.id && (
                      <IconButton name="trash" size="sm" onClick={async () => {
                        try {
                          await API.lessor.unblockDates(ev.id);
                          toast({ variant: 'info', title: t('cal.blockRemoved') });
                          setSelDay(null);
                          load();
                        } catch { toast({ variant: 'error', title: 'Erreur' }); }
                      }} />
                    )}
                  </div>
                ))}
              </div>}
        </Modal>
      )}

      {blocking && (
        <Modal title={t('cal.blockDates')} desc={t('cal.blockDesc')}
          icon={{ name: 'ban', color: 'amber' }} onClose={() => setBlocking(false)}
          footer={<>
            <Button variant="ghost" onClick={() => setBlocking(false)}>{t('common.cancel')}</Button>
            <Button variant="primary" icon="check" loading={blockLoading} onClick={doBlock}>{t('cal.confirmBlock')}</Button>
          </>}>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
            <Field label={t('det.vehicle')}>
              <Select value={blockForm.vehicleId} onChange={e => setBlockForm(f => ({ ...f, vehicleId: e.target.value }))}>
                {vehicles.map(v => <option key={v.id} value={v.id}>{v.brand} {v.model} · {v.plate}</option>)}
              </Select>
            </Field>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
              <Field label={t('cal.startDate')}><Input type="date" value={blockForm.startAt} onChange={e => setBlockForm(f => ({ ...f, startAt: e.target.value }))} /></Field>
              <Field label={t('cal.endDate')}><Input type="date" value={blockForm.endAt} onChange={e => setBlockForm(f => ({ ...f, endAt: e.target.value }))} /></Field>
            </div>
            <Field label={t('det.reasonOptional')}><Input placeholder={t('cal.reasonPlaceholder')} value={blockForm.reason} onChange={e => setBlockForm(f => ({ ...f, reason: e.target.value }))} /></Field>
          </div>
        </Modal>
      )}
    </div>
  );
}

// ============================================================
// FLEET (vehicles grid)
// ============================================================
function LessorVehicles({ navigate }) {
  const t = window.I18N ? I18N.useT() : ((k) => k);
  const toast = useToast();
  const [vehicles, setVehicles] = useS2([]);
  const [lessorProfile, setLessorProfile] = useS2(null);
  const [loading, setLoading] = useS2(true);
  const [err, setErr] = useS2(false);
  const [filter, setFilter] = useS2('all');
  const [actionLoading, setActionLoading] = useS2(null);
  const [archiving, setArchiving] = useS2(null); // véhicule en attente de confirmation d'archivage
  const [archiveLoading, setArchiveLoading] = useS2(false);

  const load = () => {
    setLoading(true);
    setErr(false);
    Promise.all([
      API.lessor.getVehicles().catch(() => []),
      API.lessor.getProfile().catch(() => null),
    ])
      .then(([data, profileData]) => {
        setVehicles(Array.isArray(data) ? data.map(API.normalizeVehicle) : []);
        setLessorProfile(profileData);
        setLoading(false);
      })
      .catch(() => { setErr(true); setLoading(false); });
  };

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

  // Un véhicule archivé sort de la liste par défaut — il ne réapparaît qu'en choisissant
  // explicitement le filtre « Archivés ».
  const list = filter === 'all'
    ? vehicles.filter(v => v.status !== 'archived')
    : vehicles.filter(v => v.status === filter);
  const publishedCount = vehicles.filter(v => v.status === 'published').length;

  const doArchive = async (v) => {
    setArchiveLoading(true);
    try {
      await API.lessor.archiveVehicle(v.id);
      setVehicles(vs => vs.map(x => x.id === v.id ? { ...x, status: 'archived', archivedAt: new Date().toISOString() } : x));
      toast({ variant: 'success', title: t('fleet.archived'), desc: `${v.brand} ${v.model}` });
      setArchiving(null);
    } catch (ex) {
      toast({ variant: 'error', title: t('common.error'), desc: ex.message });
    } finally {
      setArchiveLoading(false);
    }
  };

  const doUnarchive = async (v) => {
    setActionLoading(v.id);
    try {
      await API.lessor.unarchiveVehicle(v.id);
      // `archivedAt` ne dit rien de l'état de publication/modération en dessous — on le
      // redérive comme `mapVehicleStatus` (api.jsx) à partir de ce que le véhicule normalisé a
      // gardé (`rawStatus`, `published`), plutôt que de supposer un état.
      setVehicles(vs => vs.map(x => {
        if (x.id !== v.id) return x;
        const status = x.rawStatus === 'under_review' ? 'under_review'
          : x.rawStatus === 'rejected' ? 'rejected'
          : x.published ? 'published' : 'unpublished';
        return { ...x, status, archivedAt: null };
      }));
      toast({ variant: 'success', title: t('fleet.unarchived'), desc: `${v.brand} ${v.model}` });
    } catch (ex) {
      toast({ variant: 'error', title: t('common.error'), desc: ex.message });
    } finally {
      setActionLoading(null);
    }
  };

  const toggle = async (v) => {
    const isPub = v.status === 'published';
    setActionLoading(v.id);
    try {
      if (isPub) {
        await API.lessor.unpublishVehicle(v.id);
        setVehicles(vs => vs.map(x => x.id === v.id ? { ...x, status: 'unpublished', published: false } : x));
        toast({ variant: 'info', title: t('fleet.unpublished'), desc: `${v.brand} ${v.model}` });
      } else {
        await API.lessor.publishVehicle(v.id);
        setVehicles(vs => vs.map(x => x.id === v.id ? { ...x, status: 'published', published: true } : x));
        toast({ variant: 'success', title: t('fleet.published'), desc: `${v.brand} ${v.model}` });
      }
    } catch (ex) {
      toast({ variant: 'error', title: t('common.error'), desc: ex.message });
    } finally {
      setActionLoading(null);
    }
  };

  if (loading) return <div className="page-wide"><div className="fleet">{[0,1,2,3,4,5].map(i => <div key={i} className="vcard"><Sk w="100%" h={160} r={0} /><div className="vcard-body"><Sk w="60%" h={16} /><Sk w="40%" h={13} style={{ marginTop: 8 }} /></div></div>)}</div></div>;
  if (err) return <div className="page-wide"><EmptyState variant="err" title={t('fleet.loadFailed')} action={<Button variant="secondary" icon="refresh" onClick={load}>{t('common.retry')}</Button>} /></div>;

  if (lessorProfile && lessorProfile.status !== 'approved') {
    return (
      <div className="page-wide">
        <EmptyState variant="info" icon="lock"
          title={t('fleet.accountPending')}
          desc={t('fleet.accountPendingBody')}
          action={<Button variant="primary" icon="clipboard" onClick={() => navigate('setup')}>{t('fleet.completeFile')}</Button>}
        />
      </div>
    );
  }

  return (
    <div className="page-wide">
      <div className="toolbar">
        <div>
          <h2 style={{ margin: 0, fontSize: 22, fontWeight: 800, letterSpacing: '-.5px' }}>{t('cal.myFleet')}</h2>
          <div className="muted" style={{ fontSize: 14, marginTop: 2 }}>{t('fleet.count', { n: vehicles.length, online: publishedCount })}</div>
        </div>
        <div className="spacer" />
        <Select value={filter} onChange={(e) => setFilter(e.target.value)}>
          <option value="all">{t('fleet.allStatuses')}</option>
          <option value="published">{t('status.vehicle.published')}</option>
          <option value="unpublished">{t('status.vehicle.unpublished')}</option>
          <option value="under_review">{t('status.vehicle.under_review')}</option>
          <option value="rejected">{t('status.vehicle.rejected')}</option>
          <option value="archived">{t('status.vehicle.archived')}</option>
        </Select>
        <Button variant="primary" icon="plus" onClick={() => navigate('vehicle-new')}>{t('veh.add')}</Button>
      </div>

      {list.length === 0
        ? <EmptyState variant="empty" icon="car" title={t('cal.noVehicle')} desc={t('fleet.noVehicleDesc')} action={<Button variant="primary" icon="plus" onClick={() => navigate('vehicle-new')}>{t('veh.add')}</Button>} />
        : <div className="fleet">
            {list.map(v => {
              // `label` est une clé de traduction depuis la V0.9.0 (voir data.jsx) : le repli
              // sur la valeur brute doit donc rester du TEXTE, d'où `t()` appliqué au seul cas connu.
              const stMeta = KD.VEHICLE_STATUS[v.status];
              const st = { label: stMeta ? t(stMeta.label) : v.status, cls: stMeta ? stMeta.cls : 'gray' };
              const overlay = v.status === 'under_review' ? 'amber' : v.status === 'rejected' ? 'rose' : null;
              const gray = v.status === 'unpublished' || v.status === 'archived';
              return (
                <div key={v.id} className="vcard">
                  <div className="vcard-photo">
                    <VehiclePhoto vehicle={v} w="100%" h="100%" radius={0} grayscale={gray} />
                    {overlay && <div className={`vcard-overlay ${overlay}`}><div className="vcard-status-mid"><Icon name={v.status === 'rejected' ? 'alert' : 'clock'} size={15} />{st.label}</div></div>}
                    <div className="vcard-badge"><Badge cls={st.cls}>{st.label}</Badge></div>
                    <button className="vcard-fav"><Icon name="image" size={16} /></button>
                  </div>
                  <div className="vcard-body">
                    <div className="vc-title">{v.brand} {v.model} {v.year}</div>
                    <div className="vc-sub">{v.cat}<span className="sep" style={{ width: 3, height: 3, borderRadius: 9, background: 'var(--slate-300)', display: 'inline-block', margin: '0 4px' }} /><Icon name="pin" size={13} />{v.city}</div>
                    <div className="vcard-specs">
                      {v.seats && <span className="vspec"><Icon name="seats" size={15} />{v.seats}</span>}
                      {v.fuel  && <span className="vspec"><Icon name="fuel" size={15} />{v.fuel}</span>}
                      {v.trans && <span className="vspec"><Icon name="cog" size={15} />{v.trans}</span>}
                    </div>
                    <div className="vcard-foot">
                      <div className="vcard-price">{KD.fmtNum(v.price)} <span>DA / jour</span></div>
                      {v.reviews > 0 && <div className="vcard-rating"><Icon name="star" size={14} style={{ color: 'var(--amber-500)' }} />{Number(v.rating || 0).toFixed(1)} <span className="muted">({v.reviews})</span></div>}
                    </div>
                    <div className="vcard-actions">
                      {v.status === 'archived'
                        // Un véhicule archivé n'a plus rien à faire des actions habituelles
                        // (publier/dépublier, gérer les photos, réarchiver) : la seule sortie
                        // possible est de le désarchiver.
                        ? <Button size="sm" variant="secondary" style={{ flex: 1 }} icon="upload" loading={actionLoading === v.id} onClick={() => doUnarchive(v)}>{t('fleet.unarchive')}</Button>
                        : <>
                          {v.status === 'published' && <Button size="sm" variant="secondary" style={{ flex: 1 }} icon="eyeOff" loading={actionLoading === v.id} onClick={() => toggle(v)}>{t('fleet.unpublish')}</Button>}
                          {v.status === 'unpublished' && <Button size="sm" variant="success" style={{ flex: 1 }} icon="upload" loading={actionLoading === v.id} onClick={() => toggle(v)}>{t('fleet.publish')}</Button>}
                          {(v.status === 'under_review' || v.status === 'rejected') && <Button size="sm" variant="secondary" style={{ flex: 1 }} icon="eye" disabled>{v.status === 'rejected' ? t('status.vehicle.rejected') : t('status.vehicle.under_review')}</Button>}
                          <Button size="sm" variant="secondary" icon="edit" onClick={() => navigate('vehicle-edit', { id: v.id })}>{t('cal.edit')}</Button>
                          <Dropdown align="right"
                            trigger={<button className="btn btn-secondary btn-icon sm"><Icon name="moreV" size={16} /></button>}
                            items={[
                              { icon: 'clipboard', label: t('fleet.seeBookings'), onClick: () => navigate('bookings') },
                              { icon: 'image', label: t('fleet.managePhotos'), onClick: () => navigate('vehicle-edit', { id: v.id }) },
                              { sep: true },
                              { icon: 'trash', label: t('book.delete'), danger: true, onClick: () => setArchiving(v) },
                            ]} />
                        </>}
                    </div>
                  </div>
                </div>
              );
            })}
          </div>}
      {archiving && (
        <Modal title={t('fleet.archiveConfirmTitle')} desc={t('fleet.archiveConfirmDesc')}
          icon={{ name: 'trash', color: 'rose' }} onClose={() => setArchiving(null)}
          footer={<>
            <Button variant="ghost" onClick={() => setArchiving(null)}>{t('common.cancel')}</Button>
            <Button variant="danger" icon="trash" loading={archiveLoading} onClick={() => doArchive(archiving)}>{t('book.delete')}</Button>
          </>}>
          <div>{archiving.brand} {archiving.model} {archiving.year}</div>
        </Modal>
      )}
    </div>
  );
}

// ============================================================
// SETUP — KYC checklist post-login
// ============================================================
function LessorSetup({ navigate }) {
  const t = window.I18N ? I18N.useT() : ((k) => k);
  const toast = useToast();
  const [profile, setProfile] = useS2(null);
  const [docs, setDocs] = useS2([]);
  const [loading, setLoading] = useS2(true);
  const [uploading, setUploading] = useS2(null);
  const [requesting, setRequesting] = useS2(false);
  const [activated, setActivated] = useS2(false);

  const DOC_DEFS_BASE = [
    { key: 'cin', label: t('setup.cin'), icon: 'card', required: true, hint: t('setup.cinDesc') },
    { key: 'rib', label: t('setup.rib'), icon: 'banknote', required: false, hint: t('setup.ribDesc') },
  ];

  const DOC_DEFS_AGENCY = [
    { key: 'cin', label: t('setup.cin'), icon: 'card', required: true, hint: t('setup.cinDesc') },
    { key: 'rc', label: t('setup.rc'), icon: 'clipboard', required: false, hint: t('setup.rcDesc') },
    { key: 'rib', label: t('setup.rib'), icon: 'banknote', required: false, hint: t('setup.ribDesc') },
  ];

  const docDefs = profile && profile.type === 'agency' ? DOC_DEFS_AGENCY : DOC_DEFS_BASE;

  const load = async () => {
    setLoading(true);
    try {
      const p = await API.lessor.getProfile();
      setProfile(p);
      if (p && p.id) {
        const d = await API.lessor.getMyDocuments(p.id).catch(() => []);
        setDocs(Array.isArray(d) ? d : []);
      }
    } catch {}
    setLoading(false);
  };

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

  const getDoc = (key) => docs.find(d => d.documentType === key);

  const handleUpload = async (key, file) => {
    if (!profile?.id) return;
    setUploading(key);
    try {
      await API.lessor.uploadDocument(profile.id, key, file);
      const updated = await API.lessor.getMyDocuments(profile.id).catch(() => []);
      setDocs(Array.isArray(updated) ? updated : []);
      toast({ variant: 'success', title: t('setup.docSent'), desc: key.toUpperCase() });
    } catch (ex) {
      toast({ variant: 'error', title: t('common.error'), desc: ex.message });
    } finally {
      setUploading(null);
    }
  };

  const cinUploaded = !!getDoc('cin');

  const requestActivation = async () => {
    setRequesting(true);
    try {
      await API.lessor.requestActivation();
      setActivated(true);
      toast({ variant: 'success', title: t('det.requestSent'), desc: t('setup.reviewIn48h') });
    } catch (ex) {
      toast({ variant: 'error', title: t('common.error'), desc: ex.message });
    } finally {
      setRequesting(false);
    }
  };

  if (loading) return <div className="page-wide"><div className="card card-pad"><Sk w="100%" h={200} r={12} /></div></div>;

  return (
    <div className="page-wide" style={{ maxWidth: 680, margin: '0 auto' }}>
      <div className="toolbar" style={{ marginBottom: 20 }}>
        <div>
          <h2 style={{ margin: 0, fontSize: 22, fontWeight: 800 }}>{t('setup.title')}</h2>
          <div className="muted" style={{ fontSize: 14, marginTop: 2 }}>{t('setup.subtitle')}</div>
        </div>
      </div>

      <Card>
        <div className="card-pad" style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>

          {/* Profile step — always done if we're here */}
          <div className="req" style={{ padding: 14 }}>
            <div className="sc-ico green" style={{ width: 40, height: 40, borderRadius: 11 }}>
              <Icon name="check" size={18} />
            </div>
            <div className="req-body">
              <div style={{ fontWeight: 700, fontSize: 14 }}>{profile?.type === 'agency' ? t('setup.profileCreatedAgency') : t('setup.profileCreatedLessor')}</div>
              <div className="muted" style={{ fontSize: 12.5 }}>{profile?.businessName}</div>
            </div>
            <span style={{ fontSize: 12, color: 'var(--emerald-600)', fontWeight: 700 }}>{t('setup.done')}</span>
          </div>

          {/* Document upload steps */}
          {docDefs.map(({ key, label, icon, required, hint }) => {
            const existing = getDoc(key);
            const status = existing?.verificationStatus;
            const isUploading = uploading === key;

            return (
              <div key={key} className="req" style={{ padding: 14 }}>
                <div className={`sc-ico ${existing ? (status === 'approved' ? 'green' : status === 'rejected' ? 'rose' : 'blue') : 'slate'}`}
                  style={{ width: 40, height: 40, borderRadius: 11 }}>
                  <Icon name={existing ? (status === 'approved' ? 'check' : icon) : icon} size={18} />
                </div>
                <div className="req-body">
                  <div style={{ fontWeight: 700, fontSize: 14 }}>{label}{required && <span style={{ color: 'var(--rose-500)' }}> *</span>}</div>
                  <div className="muted" style={{ fontSize: 12.5 }}>
                    {existing
                      ? status === 'approved' ? t('setup.verified')
                        : status === 'rejected' ? t('setup.rejected')
                        : t('setup.awaitingCheck')
                      : hint}
                  </div>
                </div>
                <div>
                  <input type="file" id={`doc-${key}`} accept=".pdf,.jpg,.jpeg,.png"
                    style={{ display: 'none' }}
                    onChange={(e) => { const f = e.target.files[0]; if (f) handleUpload(key, f); e.target.value = ''; }} />
                  <Button variant={existing && status !== 'rejected' ? 'secondary' : 'primary'}
                    size="sm" loading={isUploading}
                    onClick={() => document.getElementById(`doc-${key}`).click()}>
                    {existing ? t('setup.replace') : t('setup.upload')}
                  </Button>
                </div>
              </div>
            );
          })}
        </div>
      </Card>

      {profile && profile.status !== 'approved' && (
        <div style={{ marginTop: 20 }}>
          {activated ? (
            <div className="ob-info-box" style={{ borderRadius: 12, padding: '14px 20px' }}>
              <Icon name="check" size={15} />
              <span>{t('setup.requestSentLong')}</span>
            </div>
          ) : (
            <Button variant="primary" size="lg" className="btn-block"
              icon="send" loading={requesting}
              disabled={requesting || !cinUploaded}
              onClick={requestActivation}>
              {cinUploaded ? t('setup.requestActivation') : t('setup.needCin')}
            </Button>
          )}
          {!cinUploaded && (
            <div className="muted" style={{ textAlign: 'center', fontSize: 12.5, marginTop: 8 }}>
              {t('setup.cinRequired')}
            </div>
          )}
        </div>
      )}

      {profile && profile.status === 'approved' && (
        <div style={{ marginTop: 20, textAlign: 'center' }}>
          <div className="muted" style={{ marginBottom: 12 }}>{t('setup.accountActive')}</div>
          <Button variant="primary" icon="car" onClick={() => navigate('vehicles')}>{t('setup.manageFleet')}</Button>
        </div>
      )}
    </div>
  );
}

// AuthedImg is exported on purpose: admin1/admin2 render encrypted KYC documents with it.
Object.assign(window, { BookingDetail, LessorCalendar, LessorVehicles, LessorSetup, AuthedImg });
