/* Uleez — Promotions sur véhicule : écran LOUEUR (création) + écran ADMIN (modération).
 *
 * Parcours (décision PO) : le loueur baisse le prix d'un de ses véhicules pour une période ;
 * l'admin approuve ; alors seulement la promotion s'affiche côté client (carrousel d'accueil +
 * fiche). Une promo `pending` n'est jamais visible — c'est tout l'objet de la modération.
 *
 * ⚠️ Dépôt sans build : alias `useP`/`usePE` et constantes préfixées `PROMO_` pour ne pas
 * collisionner avec un autre fichier (une collision de `const` de premier niveau = écran BLANC).
 * L'espace loueur est traduit (i18n `promo.*`) ; l'admin reste en français littéral comme le
 * reste du back-office.
 */
const { useState: useP, useEffect: usePE } = React;

// Miroir MINIMAL de `promo-price.util` : seul le pourcentage d'affichage est recalculé ici, et
// uniquement pour l'aperçu du formulaire. Le serveur reste autoritaire (il refuse < 5 %).
function promoPercent(normal, promoted) {
  normal = Number(normal); promoted = Number(promoted);
  if (!(normal > 0) || !(promoted >= 0) || promoted >= normal) return 0;
  return Math.round(((normal - promoted) / normal) * 100);
}

const PROMO_STATUS = {
  pending:  { key: 'promo.status.pending',  cls: 'amber' },
  approved: { key: 'promo.status.approved', cls: 'green' },
  rejected: { key: 'promo.status.rejected', cls: 'rose' },
};

function fmtDateShort(iso, lang) {
  if (!iso) return '—';
  const loc = lang === 'ar' ? 'ar-DZ' : lang === 'en' ? 'en-GB' : 'fr-FR';
  try { return new Date(iso).toLocaleDateString(loc, { day: 'numeric', month: 'short', year: 'numeric' }); }
  catch (e) { return iso; }
}
function todayISO() { return new Date().toISOString().slice(0, 10); }

function promoDateTime(iso, lang) {
  if (!iso) return '—';
  const loc = lang === 'ar' ? 'ar-DZ' : lang === 'en' ? 'en-GB' : 'fr-FR';
  try {
    return new Date(iso).toLocaleString(loc, {
      day: 'numeric', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit',
    });
  } catch (e) { return iso; }
}

/* Motifs de non-visibilité renvoyés par le serveur (`visibility.reasons`), en français
 * littéral pour le back-office. L'espace loueur, lui, passe par les clés `promo.vis.*`.
 *
 * C'est la réponse à « j'ai 7-8 promotions validées et publiées, elles n'apparaissent pas
 * toutes » : l'admin lisait « approuvée » et un carrousel plus court, sans aucun moyen de
 * relier les deux. */
const PROMO_VIS_FR = {
  not_approved:     'en attente de validation',
  archived:         'retirée par le loueur',
  not_started_yet:  'la période n’a pas commencé',
  ended:            'période terminée',
  vehicle_hidden:   'véhicule non publié / suspendu',
  lessor_suspended: 'compte loueur suspendu',
  fully_booked:     'période entièrement louée',
  vehicle_missing:  'véhicule introuvable',
};

const PROMO_VIS_KEYS = {
  not_approved:     'promo.vis.notApproved',
  archived:         'promo.vis.archived',
  not_started_yet:  'promo.vis.notStarted',
  ended:            'promo.vis.ended',
  vehicle_hidden:   'promo.vis.vehicleHidden',
  lessor_suspended: 'promo.vis.lessorSuspended',
  fully_booked:     'promo.vis.fullyBooked',
  vehicle_missing:  'promo.vis.vehicleMissing',
};

/* Pastille « Visible / Pas encore visible », côté loueur (traduite) comme côté admin. */
function PromoVisibility({ r, t, lang, french }) {
  const vis = r.visibility;
  if (!vis) return null;
  if (vis.live) {
    return <Badge cls="green">{french ? 'Visible dans l’app' : t('promo.visible')}</Badge>;
  }
  const reasons = (vis.reasons || []).map((code) => {
    if (french) {
      const label = PROMO_VIS_FR[code] || code;
      return code === 'not_started_yet' ? `${label} (${fmtDateShort(r.startsAt, lang)})` : label;
    }
    return t(PROMO_VIS_KEYS[code] || code, { date: fmtDateShort(r.startsAt, lang) });
  });
  return (
    <Badge cls="amber">
      {(french ? 'Pas visible' : t('promo.notVisible')) + (reasons.length ? ` · ${reasons.join(' · ')}` : '')}
    </Badge>
  );
}

/* ─────────────────────────── LOUEUR ─────────────────────────── */
function LessorPromotions() {
  const t = window.I18N ? I18N.useT() : ((k) => k);
  const lang = window.I18N ? I18N.lang : 'fr';
  const toast = useToast();
  const [rows, setRows] = useP([]);
  const [vehicles, setVehicles] = useP([]);
  const [loading, setLoading] = useP(true);
  const [err, setErr] = useP(false);
  const [creating, setCreating] = useP(false);

  const load = () => {
    setLoading(true); setErr(false);
    Promise.all([API.vehiclePromotions.mine(), API.lessor.getVehicles()])
      .then(([mine, vs]) => {
        setRows(Array.isArray(mine) ? mine : (mine.items || []));
        const list = Array.isArray(vs) ? vs : (vs.items || vs.data || []);
        setVehicles(list);
        setLoading(false);
      })
      .catch(() => { setErr(true); setLoading(false); });
  };
  usePE(() => { load(); }, []);

  const remove = async (id) => {
    if (!window.confirm(t('promo.removeConfirm'))) return;
    try { await API.vehiclePromotions.remove(id); toast({ variant: 'success', title: t('promo.removed') }); load(); }
    catch (e) { toast({ variant: 'error', title: e && e.message }); }
  };

  return (
    <div>
      <div style={{ display: 'flex', alignItems: 'center', marginBottom: 16 }}>
        <div style={{ flex: 1 }} />
        <Button onClick={() => setCreating(true)} icon="plus" variant="primary">{t('promo.new')}</Button>
      </div>

      {loading && <Sk h={160} />}
      {!loading && err && (
        <div style={{ padding: 24, textAlign: 'center' }}>
          <Button onClick={load}>{t('bug.retry')}</Button>
        </div>
      )}
      {!loading && !err && rows.length === 0 && (
        <div style={{ padding: 24, textAlign: 'center', color: 'var(--muted)' }}>{t('promo.empty')}</div>
      )}
      {!loading && !err && rows.length > 0 && (
        <div style={{ display: 'grid', gap: 10 }}>
          {rows.map((r) => <PromoRow key={r.id} r={r} lang={lang} t={t} onRemove={() => remove(r.id)} />)}
        </div>
      )}

      {creating && (
        <PromoCreateModal
          t={t} lang={lang} vehicles={vehicles}
          onClose={() => setCreating(false)}
          onCreated={() => { setCreating(false); toast({ variant: 'success', title: t('promo.created') }); load(); }}
        />
      )}
    </div>
  );
}

function PromoRow({ r, lang, t, onRemove }) {
  const meta = PROMO_STATUS[r.moderationStatus] || PROMO_STATUS.pending;
  const v = r.vehicle || {};
  return (
    <div style={{ border: '1px solid var(--line)', borderRadius: 14, padding: 14, background: 'var(--surface)', display: 'flex', gap: 12, alignItems: 'flex-start', flexWrap: 'wrap' }}>
      <div style={{ flex: 1, minWidth: 220 }}>
        <div style={{ display: 'flex', gap: 6, alignItems: 'center', marginBottom: 4, flexWrap: 'wrap' }}>
          <Badge cls={meta.cls}>{t(meta.key)}</Badge>
          {r.discountPercent > 0 && <Badge cls="amber">-{r.discountPercent}%</Badge>}
          {/* « Approuvée » ne veut pas dire « visible » : la période peut être entièrement
              louée, ou n'avoir pas commencé. Le loueur appelait le support pour le savoir. */}
          {r.moderationStatus === 'approved' && <PromoVisibility r={r} t={t} lang={lang} />}
        </div>
        <div style={{ fontWeight: 700, fontSize: 14 }}>{v.brand} {v.model}</div>
        <div style={{ color: 'var(--muted)', fontSize: 12.5, marginTop: 2 }}>
          <span style={{ textDecoration: 'line-through' }}>{Math.round(r.normalDailyPrice)} </span>
          <strong style={{ color: 'var(--ink)' }}>{Math.round(r.promoDailyPrice)} {t('promo.perDay')}</strong>
          {' · '}{fmtDateShort(r.startsAt, lang)} → {fmtDateShort(r.endsAt, lang)}
        </div>
        {r.moderationStatus === 'rejected' && r.rejectionReason && (
          <div style={{ marginTop: 6, fontSize: 12.5, color: 'var(--rose-600)' }}>
            {t('promo.rejectedBecause')} : {r.rejectionReason}
          </div>
        )}
        {r.stats && (
          <div style={{ marginTop: 6, fontSize: 12.5, color: 'var(--muted)' }}>
            {r.stats.bookingsInWindow > 0
              ? t('promo.stats.bookings', { count: r.stats.bookingsInWindow })
              : t('promo.stats.none')}
          </div>
        )}
      </div>
      {!r.archivedAt && <Button variant="secondary" onClick={onRemove}>{t('promo.remove')}</Button>}
    </div>
  );
}

function PromoCreateModal({ t, lang, vehicles, onClose, onCreated }) {
  const toast = useToast();
  const [vehicleId, setVehicleId] = useP(vehicles[0] ? vehicles[0].id : '');
  const [promoPrice, setPromoPrice] = useP('');
  const [from, setFrom] = useP(todayISO());
  const [to, setTo] = useP('');
  const [saving, setSaving] = useP(false);
  const [avail, setAvail] = useP(null);
  const [availLoading, setAvailLoading] = useP(false);

  const chosen = vehicles.find((v) => v.id === vehicleId);
  const normal = chosen ? Number(chosen.dailyPriceBase != null ? chosen.dailyPriceBase : chosen.price) : 0;
  const percent = promoPrice ? promoPercent(normal, promoPrice) : 0;

  // Bornes de journée identiques à celles de l'envoi : interroger le serveur sur d'autres
  // bornes que celles qu'on lui soumettra ensuite ferait mentir l'aperçu.
  const startsIso = from ? new Date(from + 'T00:00:00').toISOString() : '';
  const endsIso = to ? new Date(to + 'T23:59:59').toISOString() : '';

  /* Jours déjà loués sur la période choisie.
   *
   * Le serveur REFUSE une promotion sur un véhicule loué de bout en bout (409) : sans cet
   * aperçu, le loueur ne l'apprendrait qu'en cliquant, après avoir tout saisi. Et une période
   * partiellement louée est acceptée — autant le dire, c'est une information utile pour
   * choisir ses dates.
   *
   * Débounce de 350 ms : les champs `date` émettent à chaque frappe de l'année. */
  usePE(() => {
    if (!vehicleId || !startsIso || !endsIso || new Date(endsIso) <= new Date(startsIso)) {
      setAvail(null);
      return undefined;
    }
    let alive = true;
    setAvailLoading(true);
    const timer = setTimeout(() => {
      API.vehiclePromotions.availability(vehicleId, startsIso, endsIso)
        .then((d) => { if (alive) { setAvail(d); setAvailLoading(false); } })
        // Un aperçu indisponible ne doit pas bloquer la création : le serveur tranchera.
        .catch(() => { if (alive) { setAvail(null); setAvailLoading(false); } });
    }, 350);
    return () => { alive = false; clearTimeout(timer); setAvailLoading(false); };
  }, [vehicleId, startsIso, endsIso]);

  const submit = async () => {
    if (!vehicleId) return;
    if (!to || new Date(to) <= new Date(from)) { toast({ variant: 'error', title: t('promo.endsAfterStart') }); return; }
    if (percent < 5) { toast({ variant: 'error', title: t('promo.minDiscount') }); return; }
    setSaving(true);
    try {
      await API.vehiclePromotions.create({
        vehicleId,
        promoDailyPrice: Number(promoPrice),
        // Bornes de journée : début à 00:00, fin à 23:59:59 locale, pour que « jusqu'au 20 »
        // couvre bien tout le 20.
        startsAt: startsIso,
        endsAt: endsIso,
      });
      onCreated();
    } catch (e) {
      toast({ variant: 'error', title: t('promo.createFailed'), desc: e && e.message });
    } finally { setSaving(false); }
  };

  return (
    <Modal title={t('promo.new')} onClose={onClose}>
      <div style={{ display: 'grid', gap: 14 }}>
        <Field label={t('promo.vehicle')}>
          <Select value={vehicleId} onChange={(e) => setVehicleId(e.target.value)}>
            {vehicles.length === 0 && <option value="">{t('promo.pickVehicle')}</option>}
            {vehicles.map((v) => (
              <option key={v.id} value={v.id}>
                {v.brand} {v.model} — {Math.round(Number(v.dailyPriceBase != null ? v.dailyPriceBase : v.price))} DA
              </option>
            ))}
          </Select>
        </Field>

        <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap' }}>
          <Field label={t('promo.normalPrice')}>
            <Input value={normal ? `${Math.round(normal)} DA` : '—'} disabled />
          </Field>
          <Field label={t('promo.promoPrice')}>
            <Input type="number" min="0" value={promoPrice} onChange={(e) => setPromoPrice(e.target.value)} />
          </Field>
          <Field label={t('promo.discount')}>
            <Input value={percent ? `-${percent} %` : '—'} disabled />
          </Field>
        </div>

        <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap' }}>
          <Field label={t('promo.from')}>
            <Input type="date" value={from} min={todayISO()} onChange={(e) => setFrom(e.target.value)} />
          </Field>
          <Field label={t('promo.to')}>
            <Input type="date" value={to} min={from} onChange={(e) => setTo(e.target.value)} />
          </Field>
        </div>

        {(availLoading || avail) && (
          <div style={{
            fontSize: 12.5, borderRadius: 12, padding: '10px 12px',
            background: avail && avail.fullyBooked ? 'var(--rose-50, rgba(244,63,94,.08))' : 'var(--surface-2, rgba(127,127,127,.07))',
            color: avail && avail.fullyBooked ? 'var(--rose-600)' : 'var(--muted)',
          }}>
            {availLoading && t('promo.availChecking')}
            {!availLoading && avail && avail.fullyBooked && t('promo.availFull')}
            {!availLoading && avail && !avail.fullyBooked && avail.totalDays > 0 && (
              avail.freeDays >= avail.totalDays
                ? t('promo.availFree', { total: avail.totalDays })
                : t('promo.availPartial', {
                    booked: Math.max(0, avail.totalDays - avail.freeDays),
                    total: avail.totalDays,
                    free: avail.freeDays,
                  })
            )}
            {!availLoading && avail && (avail.bookedRanges || []).length > 0 && (
              <div style={{ marginTop: 4 }}>
                {t('promo.availBookedOn')}{' '}
                {avail.bookedRanges
                  .map((b) => `${fmtDateShort(b.from, lang)} → ${fmtDateShort(b.to, lang)}`)
                  .join(' · ')}
              </div>
            )}
          </div>
        )}
        <div style={{ fontSize: 12.5, color: 'var(--muted)' }}>{t('promo.pendingNote')}</div>
        <Button
          variant="primary"
          loading={saving}
          disabled={saving || (avail != null && avail.fullyBooked)}
          onClick={submit}
        >{saving ? t('promo.creating') : t('promo.create')}</Button>
      </div>
    </Modal>
  );
}

/* ─────────────────────────── ADMIN ─────────────────────────── */
/* La file de modération ne montrait que sept données : remise, marque/modèle, prix barré, prix
 * promo, période et ville. Un admin ne pouvait donc pas répondre aux deux questions qu'il se
 * pose vraiment — « de quel loueur s'agit-il ? » et « pourquoi cette promotion approuvée
 * n'apparaît-elle pas dans l'app ? ». Le serveur renvoie désormais le loueur (sa relation
 * était déjà chargée puis jetée), les traces de modération, la visibilité motivée et les
 * chiffres de la période ; cet écran les affiche, et ouvre un dossier au clic.
 */
const PROMO_ADMIN_TABS = [
  { id: 'pending',  label: 'À valider' },
  { id: 'approved', label: 'En ligne' },
  { id: 'rejected', label: 'Refusées' },
];

const PROMO_PAGE_SIZE = 50;

function promoKv(k, v, opts = {}) {
  return (
    <div className="kv">
      <span className="k">{k}</span>
      <span className={`v${opts.strong ? ' strong' : ''}${opts.mono ? ' mono' : ''}`}>
        {v == null || v === '' ? '—' : v}
      </span>
    </div>
  );
}

function AdminVehiclePromotions() {
  const lang = window.I18N ? I18N.lang : 'fr';
  const toast = useToast();
  const [tab, setTab] = useP('pending');
  const [page, setPage] = useP(1);
  const [rows, setRows] = useP([]);
  const [total, setTotal] = useP(0);
  const [loading, setLoading] = useP(true);
  const [err, setErr] = useP(false);
  const [rejecting, setRejecting] = useP(null);
  const [reason, setReason] = useP('');
  const [saving, setSaving] = useP(false);
  const [detail, setDetail] = useP(null);

  const load = () => {
    setLoading(true); setErr(false);
    API.vehiclePromotions.staffList({ status: tab, page, limit: PROMO_PAGE_SIZE })
      .then((res) => { setRows((res && res.items) || []); setTotal((res && res.total) || 0); setLoading(false); })
      .catch(() => { setErr(true); setLoading(false); });
  };
  usePE(() => { load(); }, [tab, page]);

  const approve = async (id) => {
    setSaving(true);
    try {
      await API.vehiclePromotions.moderate(id, { moderationStatus: 'approved' });
      toast({ variant: 'success', title: 'Promotion approuvée' });
      setDetail(null);
      load();
    }
    catch (e) { toast({ variant: 'error', title: e && e.message }); }
    finally { setSaving(false); }
  };
  const reject = async () => {
    if (!rejecting) return;
    if (!reason.trim()) { toast({ variant: 'error', title: 'Un motif est requis pour refuser.' }); return; }
    setSaving(true);
    try {
      await API.vehiclePromotions.moderate(rejecting.id, { moderationStatus: 'rejected', rejectionReason: reason });
      toast({ variant: 'success', title: 'Promotion refusée' });
      setRejecting(null); setReason(''); setDetail(null); load();
    } catch (e) { toast({ variant: 'error', title: e && e.message }); }
    finally { setSaving(false); }
  };

  // Combien d'approuvées ne sont PAS visibles des clients : c'est le chiffre qui explique un
  // carrousel plus court que la file, et il se lit d'un coup d'œil en haut de l'écran.
  const hidden = rows.filter((r) => r.moderationStatus === 'approved' && r.visibility && !r.visibility.live).length;
  const pages = Math.max(1, Math.ceil(total / PROMO_PAGE_SIZE));

  return (
    <div>
      <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginBottom: 16, alignItems: 'center' }}>
        {PROMO_ADMIN_TABS.map((tb) => (
          <Chip key={tb.id} active={tab === tb.id} onClick={() => { setTab(tb.id); setPage(1); }}>{tb.label}</Chip>
        ))}
        <div style={{ flex: 1 }} />
        {!loading && !err && (
          <div style={{ color: 'var(--muted)', fontSize: 13 }}>
            {total} promotion{total > 1 ? 's' : ''}
            {tab === 'approved' && hidden > 0 && (
              <span style={{ color: 'var(--amber-600, #d97706)', fontWeight: 700 }}>
                {' '}· {hidden} non visible{hidden > 1 ? 's' : ''} dans l’app
              </span>
            )}
          </div>
        )}
      </div>

      {loading && <Sk h={160} />}
      {!loading && err && <div style={{ padding: 24, textAlign: 'center' }}><Button onClick={load}>Réessayer</Button></div>}
      {!loading && !err && rows.length === 0 && (
        <EmptyState icon="gift" title="Aucune promotion dans cette vue."
          desc="Les promotions sont créées par les loueurs depuis leur espace, puis validées ici." />
      )}
      {!loading && !err && rows.length > 0 && (
        <div style={{ display: 'grid', gap: 10 }}>
          {rows.map((r) => (
            <PromoAdminRow
              key={r.id} r={r} lang={lang} saving={saving}
              onOpen={() => setDetail(r)}
              onApprove={() => approve(r.id)}
              onReject={() => { setRejecting(r); setReason(''); }}
            />
          ))}
        </div>
      )}

      {!loading && !err && pages > 1 && (
        <div style={{ display: 'flex', gap: 8, justifyContent: 'center', marginTop: 16, alignItems: 'center' }}>
          <Button variant="secondary" disabled={page <= 1} onClick={() => setPage(page - 1)}>Précédent</Button>
          <span style={{ color: 'var(--muted)', fontSize: 13 }}>Page {page} / {pages}</span>
          <Button variant="secondary" disabled={page >= pages} onClick={() => setPage(page + 1)}>Suivant</Button>
        </div>
      )}

      {detail && (
        <PromoAdminDetail
          r={detail} lang={lang} saving={saving}
          onClose={() => setDetail(null)}
          onApprove={() => approve(detail.id)}
          onReject={() => { setRejecting(detail); setReason(''); }}
        />
      )}

      {rejecting && (
        <Modal title="Refuser la promotion" onClose={() => setRejecting(null)}>
          <div style={{ display: 'grid', gap: 12 }}>
            <div style={{ fontSize: 13, color: 'var(--muted)' }}>
              Le loueur verra ce motif et pourra corriger. Soyez précis.
            </div>
            <Field label="Motif du refus">
              <textarea className="input" rows={3} value={reason} onChange={(e) => setReason(e.target.value)}
                placeholder="Ex : la remise annoncée ne correspond pas au prix habituel de ce véhicule." />
            </Field>
            <Button variant="primary" loading={saving} onClick={reject}>Confirmer le refus</Button>
          </div>
        </Modal>
      )}
    </div>
  );
}

function PromoAdminRow({ r, lang, saving, onOpen, onApprove, onReject }) {
  const v = r.vehicle || {};
  const l = r.lessor || {};
  const st = r.stats;
  const photo = v.coverPhotoUrl ? API.resolveUrl(v.coverPhotoUrl) : null;
  return (
    <div
      onClick={onOpen}
      style={{
        border: '1px solid var(--line)', borderRadius: 14, padding: 14, background: 'var(--surface)',
        display: 'flex', gap: 12, alignItems: 'flex-start', flexWrap: 'wrap', cursor: 'pointer',
      }}
    >
      {photo && (
        <img src={photo} alt="" style={{ width: 72, height: 54, objectFit: 'cover', borderRadius: 10, display: 'block' }} />
      )}
      <div style={{ flex: 1, minWidth: 240 }}>
        <div style={{ display: 'flex', gap: 6, alignItems: 'center', marginBottom: 4, flexWrap: 'wrap' }}>
          {r.discountPercent > 0 && <Badge cls="amber">-{r.discountPercent}%</Badge>}
          <span style={{ fontWeight: 700, fontSize: 14 }}>{v.brand} {v.model}</span>
          {v.registrationNumber && (
            <span className="mono" style={{ fontSize: 12, color: 'var(--muted)' }}>{v.registrationNumber}</span>
          )}
          {r.moderationStatus === 'approved' && <PromoVisibility r={r} lang={lang} french />}
        </div>

        {/* Le loueur, enfin nommé : c'est la première chose qu'un admin cherche. */}
        <div className="celluser" style={{ marginBottom: 6 }}>
          <Avatar name={l.businessName || '—'} size={28} square />
          <div className="cu-meta">
            <div className="n">{l.businessName || 'Loueur inconnu'}</div>
            <div className="e">
              {[l.phone, l.city || l.wilaya].filter(Boolean).join(' · ') || '—'}
              {l.status && l.status !== 'approved' ? ` · compte ${l.status}` : ''}
            </div>
          </div>
        </div>

        <div style={{ color: 'var(--muted)', fontSize: 12.5 }}>
          <span style={{ textDecoration: 'line-through' }}>{Math.round(r.normalDailyPrice)} </span>
          <strong style={{ color: 'var(--ink)' }}>{Math.round(r.promoDailyPrice)} DA/jour</strong>
          {' · '}{fmtDateShort(r.startsAt, lang)} → {fmtDateShort(r.endsAt, lang)}
          {v.pickupCity ? ` · ${v.pickupCity}` : ''}
          {' · déposée le '}{fmtDateShort(r.createdAt, lang)}
        </div>

        {st && (
          <div style={{ color: 'var(--muted)', fontSize: 12.5, marginTop: 4 }}>
            {st.bookingsInWindow} réservation{st.bookingsInWindow > 1 ? 's' : ''} sur la période
            {st.bookingsAtPromoPrice > 0 ? ` (dont ${st.bookingsAtPromoPrice} au prix promo)` : ''}
            {st.revenueInWindow > 0 ? ` · ${KD.fmtDA(st.revenueInWindow)}` : ''}
            {st.daysTotal > 0 ? ` · ${st.daysBooked}/${st.daysTotal} j loués` : ''}
          </div>
        )}

        {r.moderationStatus === 'rejected' && r.rejectionReason && (
          <div style={{ marginTop: 6, fontSize: 12.5, color: 'var(--rose-600)' }}>Motif : {r.rejectionReason}</div>
        )}
      </div>

      {r.moderationStatus === 'pending' && (
        <div style={{ display: 'flex', gap: 8 }} onClick={(e) => e.stopPropagation()}>
          <Button variant="secondary" disabled={saving} onClick={onReject}>Refuser</Button>
          <Button variant="primary" disabled={saving} onClick={onApprove}>Approuver</Button>
        </div>
      )}
    </div>
  );
}

/* Dossier d'une promotion : tout ce que le serveur sait, plus les réservations de la période.
 *
 * Même structure que le dossier de réservation (`AdminBookingDossier`) : l'admin y retrouve
 * ses repères, et le `kv` est le même. */
function PromoAdminDetail({ r, lang, saving, onClose, onApprove, onReject }) {
  const [bookings, setBookings] = useP(null);
  const [loading, setLoading] = useP(true);
  const v = r.vehicle || {};
  const l = r.lessor || {};
  const st = r.stats || {};

  usePE(() => {
    let alive = true;
    setLoading(true);
    API.vehiclePromotions.staffBookings(r.id)
      .then((d) => { if (alive) { setBookings(d); setLoading(false); } })
      .catch(() => { if (alive) { setBookings(null); setLoading(false); } });
    return () => { alive = false; };
  }, [r.id]);

  const vis = r.visibility;

  return (
    <Modal
      wide
      icon={{ name: 'gift', color: 'amber' }}
      title={`${v.brand || ''} ${v.model || ''} · -${r.discountPercent}%`}
      desc={`${Math.round(r.promoDailyPrice)} DA/jour au lieu de ${Math.round(r.normalDailyPrice)} · ${fmtDateShort(r.startsAt, lang)} → ${fmtDateShort(r.endsAt, lang)}`}
      onClose={onClose}
      footer={r.moderationStatus === 'pending' ? (
        <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
          <Button variant="secondary" disabled={saving} onClick={onReject}>Refuser</Button>
          <Button variant="primary" disabled={saving} onClick={onApprove}>Approuver</Button>
        </div>
      ) : null}
    >
      <div style={{ display: 'grid', gap: 18 }}>
        <div>
          <div className="flabel" style={{ marginBottom: 8 }}>Visibilité côté client</div>
          {vis ? <PromoVisibility r={r} lang={lang} french /> : <span className="muted">—</span>}
          {vis && !vis.live && (
            <div style={{ fontSize: 12.5, color: 'var(--muted)', marginTop: 8 }}>
              Une promotion non visible n’apparaît ni dans le carrousel d’accueil, ni dans les
              résultats de recherche. Elle reste approuvée : elle réapparaîtra d’elle-même si la
              cause disparaît (période atteinte, véhicule republié, dates libérées).
            </div>
          )}
        </div>

        <div style={{ display: 'grid', gap: 18, gridTemplateColumns: 'repeat(auto-fit, minmax(260px, 1fr))' }}>
          <div>
            <div className="flabel" style={{ marginBottom: 8 }}>Loueur</div>
            {promoKv('Raison sociale', l.businessName, { strong: true })}
            {promoKv('Téléphone', l.phone)}
            {promoKv('E-mail', l.email)}
            {promoKv('Ville', [l.city, l.wilaya].filter(Boolean).join(', '))}
            {promoKv('Statut du compte', l.status)}
          </div>
          <div>
            <div className="flabel" style={{ marginBottom: 8 }}>Véhicule</div>
            {promoKv('Modèle', `${v.brand || ''} ${v.model || ''}`.trim(), { strong: true })}
            {promoKv('Immatriculation', v.registrationNumber, { mono: true })}
            {promoKv('Catégorie', v.category)}
            {promoKv('Ville de départ', v.pickupCity)}
            {promoKv('Annonce', v.published === false ? 'non publiée' : v.status)}
          </div>
          <div>
            <div className="flabel" style={{ marginBottom: 8 }}>Modération</div>
            {promoKv('Statut', r.moderationStatus)}
            {promoKv('Déposée le', promoDateTime(r.createdAt, lang))}
            {promoKv('Décidée le', r.reviewedAt ? promoDateTime(r.reviewedAt, lang) : null)}
            {promoKv('Par', r.reviewedBy, { mono: true })}
            {promoKv('Motif du refus', r.rejectionReason)}
          </div>
          <div>
            <div className="flabel" style={{ marginBottom: 8 }}>Sur la période</div>
            {promoKv('Réservations', st.bookingsInWindow != null ? String(st.bookingsInWindow) : null, { strong: true })}
            {promoKv('Dont au prix promo', st.bookingsAtPromoPrice != null ? String(st.bookingsAtPromoPrice) : null)}
            {promoKv('Recettes', st.revenueInWindow ? KD.fmtDA(st.revenueInWindow) : null)}
            {promoKv('Jours loués', st.daysTotal ? `${st.daysBooked} / ${st.daysTotal}` : null)}
            {promoKv('Reste libre', st.daysLeft ? `${st.daysLeftFree} / ${st.daysLeft} j` : null)}
          </div>
        </div>

        <div>
          <div className="flabel" style={{ marginBottom: 8 }}>Réservations recoupant la période</div>
          {loading && <Sk h={120} />}
          {!loading && (!bookings || bookings.items.length === 0) && (
            <EmptyState icon="calendar" title="Aucune réservation sur cette période"
              desc="Le véhicule est resté libre pendant toute la promotion." />
          )}
          {!loading && bookings && bookings.items.length > 0 && (
            <div className="tbl-wrap">
              <table className="tbl">
                <thead>
                  <tr>
                    <th>Client</th><th>Réservation</th><th>Dates</th>
                    <th className="cellnum">Montant</th><th>Statut</th>
                  </tr>
                </thead>
                <tbody>
                  {bookings.items.map((b) => (
                    <tr key={b.id}>
                      <td>{b.customer ? `${b.customer.firstName || ''} ${b.customer.lastName || ''}`.trim() : '—'}</td>
                      <td className="mono strong">
                        {b.referenceCode}
                        {/* Seule attribution dont on soit sûr : la réservation porte l'id de
                            cette promotion, donc elle a bien été prise au prix promo. */}
                        {b.atPromoPrice && <Badge cls="green" dot={false}>prix promo</Badge>}
                      </td>
                      <td className="nowrap">{fmtDateShort(b.pickupAt, lang)} → {fmtDateShort(b.returnAt, lang)}</td>
                      <td className="cellnum">{KD.fmtDA(b.totalAmount)}</td>
                      <td><StatusBadge map={KD.BOOKING_STATUS} value={b.status} /></td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          )}
        </div>
      </div>
    </Modal>
  );
}
