/* global React */
// ============================================================================
// error-tracking.jsx — Monitoring d'erreurs natif Supabase
// ----------------------------------------------------------------------------
// Capture les erreurs JS côté client et les écrit dans la table
// public.client_errors. Pas de SaaS externe, pas de DSN à configurer,
// données 100% chez vous (RGPD natif).
//
// Cap de sécurité :
//   - 1 INSERT par seconde max (throttle)
//   - 20 erreurs par session max (anti-boucle infinie)
//   - Aucune erreur dans le code de capture lui-même ne casse l'app
//
// API exposée (compatible avec l'ancienne version Sentry) :
//   window.MELRErrors.capture(err, context)
//   window.MELRErrors.setUser({id, organization_id})
//   window.MELRErrors.setContext(key, value)
//   window.MELRErrors.breadcrumb(category, message, data)
// ============================================================================

(function () {
  "use strict";

  // ── État interne ─────────────────────────────────────────────────────
  let currentUser = null;          // { id, organization_id }
  let currentRoute = null;
  let currentContext = {};         // contextes globaux ajoutés par setContext
  const breadcrumbs = [];          // anneau circulaire, garde les 20 derniers
  const BREADCRUMB_MAX = 20;
  const SESSION_MAX = 20;          // erreurs max par session navigateur
  let sessionCount = 0;
  let lastInsertTs = 0;            // throttle 1 seconde
  const recentFingerprints = new Map(); // fingerprint -> last insert ts

  // ── Helpers ──────────────────────────────────────────────────────────
  function fingerprintOf(message, stack) {
    // Empreinte = message + 1re ligne du stack (la plus pertinente)
    const firstStackLine = (stack || "").split("\n").find((l) => l.trim().length > 0) || "";
    return (String(message || "").slice(0, 200) + "|" + firstStackLine.slice(0, 200));
  }

  function pushBreadcrumb(category, message, data) {
    breadcrumbs.push({ ts: new Date().toISOString(), category: category || "app", message: String(message || ""), data: data || {} });
    if (breadcrumbs.length > BREADCRUMB_MAX) breadcrumbs.splice(0, breadcrumbs.length - BREADCRUMB_MAX);
  }

  // Buffer pour les erreurs survenues avant que window.melr.supabase soit
  // prêt (chargement async du bundle Vite). On retry périodiquement.
  const pendingFlush = [];
  let flushTimer = null;
  function scheduleFlush() {
    if (flushTimer) return;
    flushTimer = setInterval(() => {
      const sb = window.melr && window.melr.supabase;
      if (!sb) return;
      while (pendingFlush.length > 0) {
        const p = pendingFlush.shift();
        sb.from("client_errors").insert(p).then(
          () => {},
          (err) => console.warn("[MELR errors] insert failed:", err && err.message)
        );
      }
      clearInterval(flushTimer);
      flushTimer = null;
    }, 500);
  }

  function flushToSupabase(payload) {
    const sb = window.melr && window.melr.supabase;
    if (!sb) {
      pendingFlush.push(payload);
      scheduleFlush();
      return;
    }
    try {
      sb.from("client_errors").insert(payload).then(
        () => {},
        (err) => console.warn("[MELR errors] insert failed:", err && err.message)
      );
    } catch (e) {
      // Surtout pas de capture récursive — juste un console.warn discret.
      // eslint-disable-next-line no-console
      console.warn("[MELR errors] flush failed:", e && e.message);
    }
  }

  function shouldInsert(fingerprint) {
    const now = Date.now();
    // Throttle global 1 seconde
    if (now - lastInsertTs < 1000) return false;
    // Throttle per-fingerprint 10 secondes (évite spam d'une même erreur)
    const lastForFp = recentFingerprints.get(fingerprint);
    if (lastForFp && now - lastForFp < 10000) return false;
    // Cap session
    if (sessionCount >= SESSION_MAX) return false;
    return true;
  }

  function captureInternal(err, context, source) {
    try {
      // Normalise l'erreur (peut être un string, un objet, une Error)
      let message = "";
      let stack = null;
      if (err instanceof Error) {
        message = err.message || String(err);
        stack = err.stack || null;
      } else if (err && typeof err === "object") {
        message = err.message || err.error || JSON.stringify(err).slice(0, 500);
        stack = err.stack || null;
      } else {
        message = String(err || "(unknown error)");
      }
      if (!message) return;

      const fp = fingerprintOf(message, stack);
      if (!shouldInsert(fp)) return;

      lastInsertTs = Date.now();
      recentFingerprints.set(fp, lastInsertTs);
      sessionCount += 1;

      const payload = {
        user_id:         currentUser && currentUser.id || null,
        organization_id: currentUser && currentUser.organization_id || null,
        message:         String(message).slice(0, 2000),
        stack:           stack ? String(stack).slice(0, 10000) : null,
        fingerprint:     fp.slice(0, 400),
        source:          source || "manual",
        url:             (typeof window !== "undefined" && window.location && window.location.href) || null,
        user_agent:      (typeof navigator !== "undefined" && navigator.userAgent) || null,
        route:           currentRoute || null,
        breadcrumbs:     breadcrumbs.slice(),
        context:         Object.assign({}, currentContext, context || {}),
      };

      // Fire-and-forget : on n'attend pas la réponse pour ne pas bloquer.
      // Si Supabase n'est pas prêt, l'erreur est bufferisée et flushed
      // dès que possible.
      flushToSupabase(payload);

      // Aussi en console pour le dev local
      // eslint-disable-next-line no-console
      console.error("[MELR error]", message, context || "");
    } catch (e) {
      // Ne JAMAIS laisser un bug du tracker casser l'app
      // eslint-disable-next-line no-console
      console.warn("[MELR errors] capture failed:", e);
    }
  }

  // ── API publique ─────────────────────────────────────────────────────
  window.MELRErrors = {
    capture(err, context) {
      captureInternal(err, context, "manual");
    },
    setUser(user) {
      currentUser = user ? { id: user.id, organization_id: user.organization_id || null } : null;
    },
    setContext(key, value) {
      if (!key) return;
      if (value == null) delete currentContext[key];
      else currentContext[key] = value;
    },
    setRoute(route) {
      currentRoute = route || null;
    },
    breadcrumb(category, message, data) {
      pushBreadcrumb(category, message, data);
    },
    // Pour le dashboard d'admin : indique combien d'erreurs ont été
    // envoyées dans la session courante (utile pour debug).
    sessionStats() {
      return { count: sessionCount, max: SESSION_MAX };
    },
  };

  // ── Capture automatique des erreurs non gérées ─────────────────────
  window.addEventListener("error", (event) => {
    if (event && event.error) captureInternal(event.error, { filename: event.filename, line: event.lineno, col: event.colno }, "window.error");
    else if (event && event.message) captureInternal(event.message, { filename: event.filename }, "window.error");
  });
  window.addEventListener("unhandledrejection", (event) => {
    if (event && event.reason) captureInternal(event.reason, {}, "unhandledrejection");
  });

  // eslint-disable-next-line no-console
  console.info("[MELR errors] Capture active · destination = Supabase (table client_errors)");
})();
