// RevisionDiff.jsx — Composant de diff visuel pour une révision.
//
// Affiche side-by-side l'état actuel ("Actuel") et l'état proposé ("Proposé").
// Compare contre l'état actuel de la fiche (passé en prop), pas contre
// base_data — on veut montrer au modérateur ce qui va RÉELLEMENT changer
// au moment de la décision. base_data est utilisé séparément pour détecter
// la dérive (la fiche a-t-elle été modifiée entre la soumission et la
// décision ?) et afficher un avertissement en haut.

// ── Helpers ──────────────────────────────────────────────────────────────

// Aplatit récursivement un objet pour produire une liste de chemins.
// Arrays scalaires : valeur entière. Arrays d'objets : indexés.
function flattenPathsClient(obj, prefix = "") {
  const out = new Map();
  if (obj == null || typeof obj !== "object") {
    if (prefix) out.set(prefix, obj);
    return out;
  }
  if (Array.isArray(obj)) {
    const allScalar = obj.every(v => v == null || typeof v !== "object");
    if (allScalar) { out.set(prefix, obj); return out; }
    obj.forEach((item, i) => {
      const sub = flattenPathsClient(item, `${prefix}[${i}]`);
      for (const [k, v] of sub) out.set(k, v);
    });
    return out;
  }
  for (const [key, value] of Object.entries(obj)) {
    const path = prefix ? `${prefix}.${key}` : key;
    if (value == null || typeof value !== "object" ||
        (Array.isArray(value) && value.every(v => v == null || typeof v !== "object"))) {
      out.set(path, value);
    } else {
      const sub = flattenPathsClient(value, path);
      for (const [k, v] of sub) out.set(k, v);
    }
  }
  return out;
}

function valuesEqualClient(a, b) {
  if (a === b) return true;
  if (a == null && b == null) return true;
  if (Array.isArray(a) && Array.isArray(b)) {
    if (a.length !== b.length) return false;
    return a.every((v, i) => v === b[i]);
  }
  return false;
}

// Labels humains pour les chemins JSON. Fallback sur le path brut pour
// les paths imbriqués/array non listés.
const PATH_LABELS = {
  nom: "Nom commun",
  latin: "Nom latin",
  auteur: "Auteur taxonomique",
  genre: "Genre",
  famille: "Famille",
  statut: "Statut",
  hauteur: "Hauteur",
  altitude: "Altitude",
  couleur: "Couleur dominante",
  incipit: "Incipit",
  morphologie: "Morphologie",
  habitat: "Habitat",
  histoire: "Histoire",
  symbolique: "Symbolique",
  precautions: "Précautions",
  bioindication: "Bio-indication",
  floraison: "Floraison",
  origine: "Origine",
  usage: "Usages",
  voirAussi: "Voir aussi",
  voirAussiTitre: "Pôle thérapeutique",
  contributeur: "Contributeur (legacy)",
  "medicinal.niveau": "Médicinal · niveau",
  "medicinal.detail": "Médicinal · détail",
  "medicinal.composes": "Médicinal · composés",
  "comestibilite.niveau": "Comestibilité · niveau",
  "comestibilite.detail": "Comestibilité · détail",
};

function labelForPath(path) {
  if (PATH_LABELS[path]) return PATH_LABELS[path];
  // Fallback : capitalise le premier segment
  const seg = path.split(".")[0].split("[")[0];
  if (PATH_LABELS[seg]) return `${PATH_LABELS[seg]} (${path})`;
  return path;
}

// Rend une valeur pour affichage diff (gère string, array, object, null).
function renderValue(v) {
  if (v == null) return <em style={{ color: "var(--ink-mute)" }}>(vide)</em>;
  if (Array.isArray(v)) {
    if (v.length === 0) return <em style={{ color: "var(--ink-mute)" }}>(liste vide)</em>;
    return v.join(", ");
  }
  if (typeof v === "object") {
    return <em style={{ color: "var(--ink-mute)" }}>{`(structure)`}</em>;
  }
  const s = String(v);
  // Limite à 600 chars pour le diff (pour les longs textes)
  if (s.length > 600) return s.slice(0, 600) + "…";
  return s;
}

// ── Composants ─────────────────────────────────────────────────────────

function DiffRow({ path, before, after }) {
  if (path.startsWith("photos.")) {
    return <PhotoDiff path={path} before={before} after={after} />;
  }
  return (
    <div className="diff-row">
      <div className="diff-path">{labelForPath(path)}</div>
      <div className="diff-cols">
        <div className="diff-before">
          <span className="diff-tag">Actuel</span>
          <div className="diff-content">{renderValue(before)}</div>
        </div>
        <div className="diff-after">
          <span className="diff-tag">Proposé</span>
          <div className="diff-content">{renderValue(after)}</div>
        </div>
      </div>
    </div>
  );
}

function PhotoDiff({ path, before, after }) {
  const key = path.slice("photos.".length);
  // before = soit la dataURL (base64) actuellement en base, soit juste la clé
  //          (si on l'a snapshot via fetchPlantBaseSnapshot qui ne garde
  //          que les clés). after = dataURL complète si ajout.
  const hadBefore = before != null && before !== "";
  const hasAfter = after != null && after !== "";

  if (hadBefore && !hasAfter) {
    return (
      <div className="diff-row diff-photo">
        <div className="diff-path">Photo {key.toUpperCase()}</div>
        <div className="diff-photo-action diff-photo-del">
          <span className="diff-tag">Suppression proposée</span>
        </div>
      </div>
    );
  }
  if (!hadBefore && hasAfter) {
    const isDataURL = typeof after === "string" && after.startsWith("data:image/");
    return (
      <div className="diff-row diff-photo">
        <div className="diff-path">Photo {key.toUpperCase()}</div>
        <div className="diff-photo-action diff-photo-add">
          <span className="diff-tag">Ajout proposé</span>
          {isDataURL && (
            <img src={after} alt={key} style={{ width: 80, height: 80, objectFit: "cover", marginLeft: 12, border: "1px solid var(--line)" }} />
          )}
        </div>
      </div>
    );
  }
  // Remplacement
  return (
    <div className="diff-row diff-photo">
      <div className="diff-path">Photo {key.toUpperCase()}</div>
      <div className="diff-photo-action diff-photo-replace">
        <span className="diff-tag">Remplacement</span>
        {typeof after === "string" && after.startsWith("data:image/") && (
          <img src={after} alt={key} style={{ width: 80, height: 80, objectFit: "cover", marginLeft: 12, border: "1px solid var(--line)" }} />
        )}
      </div>
    </div>
  );
}

function RevisionDiff({ revision, currentBase }) {
  const action = revision.action;

  // ── Cas particulier : création complète ─────────────────────────
  if (action === "create") {
    const p = revision.proposed_data || {};
    return (
      <div className="revision-diff">
        <div className="diff-warning" style={{ background: "#ecfdf3", borderColor: "#12b76a", color: "#054f31" }}>
          Création d'une nouvelle fiche · « {p.nom || "—"} » ({p.latin || "—"})
        </div>
        <div style={{ display: "grid", gap: 16, marginTop: 16 }}>
          {["nom", "latin", "auteur", "famille", "hauteur", "couleur", "incipit", "morphologie", "habitat", "histoire", "symbolique"]
            .filter(f => p[f])
            .map(f => (
              <div key={f}>
                <div className="diff-path">{labelForPath(f)}</div>
                <div style={{ fontFamily: "var(--serif)", fontSize: 15, lineHeight: 1.55, color: "var(--ink-soft)", padding: "8px 12px", background: "#ecfdf3", borderLeft: "3px solid #12b76a" }}>
                  {p[f]}
                </div>
              </div>
            ))}
        </div>
      </div>
    );
  }

  // ── Cas particulier : suppression de fiche ──────────────────────
  if (action === "delete_fiche") {
    return (
      <div className="revision-diff">
        <div className="diff-warning" style={{ background: "#fef3f2", borderColor: "#d92d20", color: "#7a271a" }}>
          ⚠ Suppression définitive proposée de la fiche « {currentBase?.nom || revision.plant_id} »
        </div>
        <p style={{ fontFamily: "var(--serif)", fontSize: 15, color: "var(--ink-soft)", marginTop: 12 }}>
          Si vous approuvez, la fiche sera supprimée de manière irréversible.
        </p>
      </div>
    );
  }

  // ── action === "update" ─────────────────────────────────────────
  const baseFlat = flattenPathsClient(revision.base_data || {});
  const currentFlat = flattenPathsClient(currentBase || {});
  const propFlat = flattenPathsClient(revision.proposed_data || {});

  // Détection de dérive : champs qui ont changé en base entre le submit
  // et maintenant (peut alarmer le modérateur si une approbation va
  // écraser un changement intermédiaire d'un autre contributeur).
  const driftedPaths = [];
  const allBaseAndCurrent = new Set([...baseFlat.keys(), ...currentFlat.keys()]);
  for (const path of allBaseAndCurrent) {
    if (path === "id" || path.startsWith("contributeurs") || path.startsWith("photos.")) continue;
    if (!valuesEqualClient(baseFlat.get(path), currentFlat.get(path))) {
      driftedPaths.push(path);
    }
  }

  // Diff effectif : on compare contre l'ÉTAT ACTUEL, pas contre base_data
  // (le modérateur veut savoir ce qui va changer maintenant)
  const allPaths = new Set([...currentFlat.keys(), ...propFlat.keys()]);
  const ignored = new Set(["id", "contributeurs", "updated_at", "created_at", "updatedAt", "createdAt", "wikipedia"]);
  const changedRows = [];
  for (const path of allPaths) {
    if (ignored.has(path)) continue;
    if (path.startsWith("contributeurs")) continue;
    if (path === "wikipedia" || path.startsWith("wikipedia.")) continue;
    if (!valuesEqualClient(currentFlat.get(path), propFlat.get(path))) {
      changedRows.push({ path, before: currentFlat.get(path), after: propFlat.get(path) });
    }
  }

  // Tri : nom puis latin en premier, puis ordre alphabétique
  const order = ["nom", "latin", "auteur", "famille", "hauteur", "altitude", "couleur", "incipit"];
  changedRows.sort((a, b) => {
    const ai = order.indexOf(a.path);
    const bi = order.indexOf(b.path);
    if (ai !== -1 && bi !== -1) return ai - bi;
    if (ai !== -1) return -1;
    if (bi !== -1) return 1;
    return a.path.localeCompare(b.path);
  });

  return (
    <div className="revision-diff">
      {driftedPaths.length > 0 && (
        <div className="diff-warning">
          ⚠ <strong>La fiche a été modifiée par un autre contributeur depuis la soumission.</strong>
          {" "}Champs concernés : {driftedPaths.slice(0, 5).join(", ")}
          {driftedPaths.length > 5 && ` (+${driftedPaths.length - 5})`}.
          Examiner les différences avec attention avant d'approuver.
        </div>
      )}
      {changedRows.length === 0 ? (
        <p style={{ fontFamily: "var(--serif)", fontStyle: "italic", color: "var(--ink-mute)" }}>
          Aucune différence détectable entre la proposition et l'état actuel.
          La fiche a peut-être été modifiée entre-temps pour intégrer ces changements.
        </p>
      ) : (
        changedRows.map(row => (
          <DiffRow key={row.path} path={row.path} before={row.before} after={row.after} />
        ))
      )}
    </div>
  );
}

window.RevisionDiff = RevisionDiff;
