/* Uleez — Admin screens part 5 : signalements de bug venus de l'app mobile
 *
 * POURQUOI CET ÉCRAN. Les problèmes remontaient par capture d'écran sur WhatsApp, décrits de
 * mémoire : ni version de l'app, ni appareil, ni parcours. L'app joint désormais ce contexte
 * elle-même (voir `BugReportsService` côté backend) ; cet écran est là pour le lire.
 *
 * ⚠️ DEUX PRÉCAUTIONS PROPRES À CE DÉPÔT.
 *  1. Aucune étape de build : une variable hors portée n'est pas une erreur de compilation,
 *     c'est un écran BLANC en production. `node tools/check-web.mjs` avant chaque zip.
 *  2. Les `const` de premier niveau sont partagés entre tous les fichiers (ils sont concaténés
 *     dans la même portée globale) : d'où les alias `useS5`/`useE5`, et des noms préfixés
 *     `BUG_` — une collision est un SyntaxError qui blanchit TOUTE l'application.
 *
 * Tout ce qui s'affiche ici (description, note vocale, captures) a été fourni par un
 * utilisateur. React échappe le texte ; on n'utilise JAMAIS dangerouslySetInnerHTML dessus.
 * Et une capture de bug contient presque toujours des données personnelles — elle passe donc
 * par `AuthedImg`, jamais par une URL publique.
 */
const { useState: useS5, useEffect: useE5 } = React;

const BUG_TABS = [
  { id: 'new', labelKey: 'bug.tab.new' },
  { id: 'triaged', labelKey: 'bug.tab.triaged' },
  { id: 'resolved', labelKey: 'bug.tab.resolved' },
  { id: 'all', labelKey: 'bug.tab.all' },
];

const BUG_KIND_META = {
  crash: { labelKey: 'bug.kind.crash', cls: 'rose' },
  display: { labelKey: 'bug.kind.display', cls: 'blue' },
  feature: { labelKey: 'bug.kind.feature', cls: 'amber' },
  payment: { labelKey: 'bug.kind.payment', cls: 'amber' },
  other: { labelKey: 'bug.kind.other', cls: 'gray' },
};

const BUG_STATUS_META = {
  new: { labelKey: 'bug.status.new', cls: 'blue' },
  triaged: { labelKey: 'bug.status.triaged', cls: 'amber' },
  resolved: { labelKey: 'bug.status.resolved', cls: 'green' },
};

function AdminBugReports() {
  const t = window.I18N ? I18N.useT() : ((k) => k);
  const toast = useToast();
  const [tab, setTab] = useS5('new');
  const [rows, setRows] = useS5([]);
  const [total, setTotal] = useS5(0);
  const [loading, setLoading] = useS5(true);
  const [err, setErr] = useS5(false);
  const [viewing, setViewing] = useS5(null);
  const [note, setNote] = useS5('');
  const [saving, setSaving] = useS5(false);

  const load = () => {
    setLoading(true);
    setErr(false);
    API.bugReports
      .list(tab === 'all' ? {} : { status: tab })
      .then((res) => {
        setRows((res && res.items) || []);
        setTotal((res && res.total) || 0);
        setLoading(false);
      })
      .catch(() => {
        setErr(true);
        setLoading(false);
      });
  };
  useE5(() => {
    load();
  }, [tab]);

  const open = (r) => {
    setViewing(r);
    setNote(r.adminNote || '');
  };

  const setStatus = async (status) => {
    if (!viewing) return;
    setSaving(true);
    try {
      await API.bugReports.update(viewing.id, { status, adminNote: note });
      // `toast` prend un OBJET, pas (message, type) — une chaîne serait spreadée et
      // n'afficherait rien.
      toast({ variant: 'success', title: t('bug.saved'), desc: t(BUG_STATUS_META[status].labelKey) });
      setViewing(null);
      load();
    } catch (e) {
      toast({ variant: 'error', title: t('bug.saveFailed'), desc: e && e.message });
    } finally {
      setSaving(false);
    }
  };

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

  const kindBadge = (kind) => {
    const meta = BUG_KIND_META[kind] || BUG_KIND_META.other;
    return <Badge cls={meta.cls}>{t(meta.labelKey)}</Badge>;
  };
  const statusBadge = (status) => {
    const meta = BUG_STATUS_META[status] || BUG_STATUS_META.new;
    return <Badge cls={meta.cls}>{t(meta.labelKey)}</Badge>;
  };

  return (
    <div>
      <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginBottom: 16 }}>
        {BUG_TABS.map((tb) => (
          <Button
            key={tb.id}
            variant={tab === tb.id ? 'primary' : 'secondary'}
            onClick={() => setTab(tb.id)}
          >
            {t(tb.labelKey)}
          </Button>
        ))}
        <div style={{ flex: 1 }} />
        {!loading && !err && (
          <div style={{ alignSelf: 'center', color: 'var(--muted)', fontSize: 13 }}>
            {total} {t('bug.count')}
          </div>
        )}
      </div>

      {loading && <Sk h={160} />}

      {!loading && err && (
        <div style={{ padding: 24, textAlign: 'center' }}>
          <div style={{ color: 'var(--muted)', marginBottom: 12 }}>{t('bug.error')}</div>
          <Button onClick={load}>{t('bug.retry')}</Button>
        </div>
      )}

      {!loading && !err && rows.length === 0 && (
        <div style={{ padding: 24, textAlign: 'center', color: 'var(--muted)' }}>{t('bug.empty')}</div>
      )}

      {!loading && !err && rows.length > 0 && (
        <div style={{ display: 'grid', gap: 10 }}>
          {rows.map((r) => (
            <div
              key={r.id}
              style={{
                border: '1px solid var(--line)',
                borderRadius: 14,
                padding: 14,
                background: 'var(--surface)',
                display: 'flex',
                gap: 12,
                alignItems: 'flex-start',
                flexWrap: 'wrap',
              }}
            >
              <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
                {kindBadge(r.kind)}
                {statusBadge(r.status)}
              </div>
              <div style={{ flex: 1, minWidth: 220 }}>
                <div style={{ fontWeight: 700, fontSize: 14, marginBottom: 2 }}>
                  {r.user ? r.user.name || r.user.email : '—'}
                </div>
                <div style={{ color: 'var(--muted)', fontSize: 12.5 }}>
                  {fmtDate(r.createdAt)}
                  {r.appVersion ? ` · ${r.appVersion}` : ''}
                  {r.platform ? ` · ${r.platform}` : ''}
                  {r.screenshotCount ? ` · ${r.screenshotCount} 🖼` : ''}
                  {r.hasVoice ? ' · 🎤' : ''}
                </div>
                {r.description && (
                  <div
                    style={{
                      marginTop: 6,
                      fontSize: 13,
                      color: 'var(--ink-2)',
                      overflow: 'hidden',
                      textOverflow: 'ellipsis',
                      whiteSpace: 'nowrap',
                      maxWidth: 520,
                    }}
                  >
                    {r.description}
                  </div>
                )}
              </div>
              <Button variant="secondary" onClick={() => open(r)}>
                {t('bug.openDetail')}
              </Button>
            </div>
          ))}
        </div>
      )}

      {viewing && (
        <Modal onClose={() => setViewing(null)} title={t('bug.title')}>
          <div style={{ display: 'grid', gap: 14 }}>
            <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
              {kindBadge(viewing.kind)}
              {statusBadge(viewing.status)}
            </div>

            <div>
              <div style={{ fontWeight: 700, fontSize: 13, marginBottom: 4 }}>{t('bug.reporter')}</div>
              <div style={{ fontSize: 13.5 }}>
                {viewing.user ? viewing.user.name : '—'}
                {viewing.user && viewing.user.email ? ` · ${viewing.user.email}` : ''}
                {viewing.user && viewing.user.phone ? ` · ${viewing.user.phone}` : ''}
              </div>
            </div>

            <div>
              <div style={{ fontWeight: 700, fontSize: 13, marginBottom: 4 }}>{t('bug.description')}</div>
              <div style={{ fontSize: 13.5, whiteSpace: 'pre-wrap', color: 'var(--ink-2)' }}>
                {viewing.description || t('bug.noDescription')}
              </div>
            </div>

            {viewing.hasVoice && (
              <div>
                <div style={{ fontWeight: 700, fontSize: 13, marginBottom: 6 }}>{t('bug.voice')}</div>
                <BugVoice path={viewing.voicePath} />
              </div>
            )}

            {viewing.screenshotCount > 0 && (
              <div>
                <div style={{ fontWeight: 700, fontSize: 13, marginBottom: 6 }}>{t('bug.screenshots')}</div>
                <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
                  {(viewing.screenshotPaths || []).map((p) => (
                    <AuthedImg
                      key={p}
                      path={p}
                      alt=""
                      fetcher={API.bugReports.objectUrl}
                      style={{
                        width: 150,
                        height: 150,
                        objectFit: 'cover',
                        borderRadius: 10,
                        border: '1px solid var(--line)',
                      }}
                    />
                  ))}
                </div>
              </div>
            )}

            {/* Le contexte technique est la raison d'être de cet écran : c'est ce qui manquait
                quand les bugs arrivaient en capture d'écran sans rien d'autre. */}
            <div>
              <div style={{ fontWeight: 700, fontSize: 13, marginBottom: 4 }}>{t('bug.context')}</div>
              <div style={{ fontSize: 13, color: 'var(--muted)', display: 'grid', gap: 2 }}>
                <div>
                  {t('bug.appVersion')} : {viewing.appVersion || '—'}
                </div>
                <div>
                  {t('bug.platform')} : {viewing.platform || '—'}
                </div>
                <div>
                  {t('bug.device')} : {viewing.deviceInfo || '—'}
                </div>
                <div>
                  {t('bug.route')} : {viewing.route || '—'}
                </div>
              </div>
            </div>

            <Field label={t('bug.note')}>
              <textarea
                className="input"
                rows={3}
                value={note}
                placeholder={t('bug.notePlaceholder')}
                onChange={(e) => setNote(e.target.value)}
              />
            </Field>

            <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
              {viewing.status !== 'triaged' && (
                <Button variant="secondary" disabled={saving} onClick={() => setStatus('triaged')}>
                  {t('bug.markTriaged')}
                </Button>
              )}
              {viewing.status !== 'resolved' && (
                <Button disabled={saving} onClick={() => setStatus('resolved')}>
                  {t('bug.markResolved')}
                </Button>
              )}
              {viewing.status === 'resolved' && (
                <Button variant="secondary" disabled={saving} onClick={() => setStatus('new')}>
                  {t('bug.reopen')}
                </Button>
              )}
            </div>
          </div>
        </Modal>
      )}
    </div>
  );
}

/* Lecteur de la note vocale. Le fichier n'est servi qu'authentifié : on le charge en blob puis
 * on le donne à <audio> par une URL d'objet, exactement comme `AuthedImg` fait pour une image.
 * Une balise <audio src="/bug-reports/…"> n'enverrait pas le jeton et afficherait un lecteur
 * muet, sans erreur visible. */
function BugVoice({ path }) {
  const [url, setUrl] = useS5(null);
  const [failed, setFailed] = useS5(false);
  useE5(() => {
    let obj = null;
    let cancelled = false;
    setUrl(null);
    setFailed(false);
    if (!path) return undefined;
    API.bugReports
      .objectUrl(path)
      .then((u) => {
        if (cancelled) {
          URL.revokeObjectURL(u);
          return;
        }
        obj = u;
        setUrl(u);
      })
      .catch(() => {
        if (!cancelled) setFailed(true);
      });
    return () => {
      cancelled = true;
      if (obj) URL.revokeObjectURL(obj);
    };
  }, [path]);
  if (failed) return <div style={{ color: 'var(--muted)', fontSize: 12.5 }}>—</div>;
  if (!url) return <Sk h={38} />;
  return <audio controls src={url} style={{ width: '100%', maxWidth: 360 }} />;
}
