const { useState: useState3, useEffect: useEffect3, useMemo: useMemo3, useRef: useRef3 } = React;

// ===== Synchronisation Firestore =====
// L'état de l'onglet Carte & missions/Effectif/Journal (missions, feux, mouvements, effectif,
// véhicules) vit désormais dans Firestore plutôt que localStorage, pour que toute l'équipe de
// coordination connectée voie les mêmes données en temps réel. Pour ne rien changer aux
// nombreux appels existants (`setRoster((r) => ...)`, `setEntries((es) => [...])`, etc. dans
// app.jsx ET ui.jsx), ces deux hooks exposent exactement la même API qu'un `useState` classique
// — ils calculent juste, en plus, le différentiel avec la valeur précédente et ne poussent vers
// Firestore que les documents réellement ajoutés/modifiés/supprimés. L'affichage local est mis
// à jour par l'écouteur `onSnapshot` (source de vérité), pas directement par le setter — ainsi
// tous les appareils convergent vers les mêmes données, y compris quand elles viennent d'un
// autre poste connecté.
async function syncCollectionDiff(name, prev, next) {
  if (!window.fb) return;
  const prevMap = new Map(prev.map((x) => [x.id, x]));
  const nextMap = new Map(next.map((x) => [x.id, x]));
  const batch = window.fb.writeBatch(window.fb.db);
  let ops = 0;
  for (const [id, item] of nextMap) {
    const before = prevMap.get(id);
    if (before && JSON.stringify(before) === JSON.stringify(item)) continue;
    const { id: _drop, ...data } = item;
    batch.set(window.fb.doc(window.fb.db, name, id), data);
    ops++;
  }
  for (const id of prevMap.keys()) {
    if (!nextMap.has(id)) { batch.delete(window.fb.doc(window.fb.db, name, id)); ops++; }
  }
  if (ops === 0) return;
  try {
    await batch.commit();
  } catch (e) {
    console.error(`Échec de synchronisation Firestore (${name})`, e);
  }
}

// `orderFields` : clauses de tri Firestore optionnelles (ex. [["time", "desc"]]) — nécessaire
// pour les entrées du journal, dont l'affichage suppose un ordre chronologique déjà établi
// (cf. groupJournalByDayAndHour) ; les autres collections sont re-triées côté affichage donc
// n'en ont pas besoin.
function useFirestoreCollection3(name, ready, orderFields) {
  const [items, setItemsRaw] = useState3([]);
  const [loaded, setLoaded] = useState3(false);
  const itemsRef = useRef3([]);

  useEffect3(() => {
    if (!ready) return;
    let col = window.fb.collection(window.fb.db, name);
    if (orderFields) col = window.fb.query(col, ...orderFields.map(([f, d]) => window.fb.orderBy(f, d)));
    const unsub = window.fb.onSnapshot(col, (snap) => {
      const next = snap.docs.map((d) => ({ id: d.id, ...d.data() }));
      itemsRef.current = next;
      setItemsRaw(next);
      setLoaded(true);
    }, (e) => console.error(`Écoute Firestore en échec (${name})`, e));
    return unsub;
  }, [ready]);

  function setItems(updater) {
    const prev = itemsRef.current;
    const next = typeof updater === "function" ? updater(prev) : updater;
    itemsRef.current = next;
    setItemsRaw(next);
    syncCollectionDiff(name, prev, next);
  }

  return [items, setItems, loaded];
}

// Même principe que `useFirestoreCollection3` mais pour un unique document (ici les réglages
// partagés du camp : jour rouge, listes de noms proposées à la création d'une mission).
function useFirestoreDoc3(pathSegments, ready, initialValue) {
  const [data, setDataRaw] = useState3(initialValue);
  const [loaded, setLoaded] = useState3(false);
  const dataRef = useRef3(initialValue);

  useEffect3(() => {
    if (!ready) return;
    const ref = window.fb.doc(window.fb.db, ...pathSegments);
    const unsub = window.fb.onSnapshot(ref, (snap) => {
      const val = snap.exists() ? snap.data() : initialValue;
      dataRef.current = val;
      setDataRaw(val);
      setLoaded(true);
    }, (e) => console.error(`Écoute Firestore en échec (${pathSegments.join("/")})`, e));
    return unsub;
  }, [ready]);

  function setData(updater) {
    const prev = dataRef.current;
    const next = typeof updater === "function" ? updater(prev) : updater;
    dataRef.current = next;
    setDataRaw(next);
    window.fb.setDoc(window.fb.doc(window.fb.db, ...pathSegments), next)
      .catch((e) => console.error(`Échec de synchronisation Firestore (${pathSegments.join("/")})`, e));
  }

  return [data, setData, loaded];
}

function App() {
  const [tab, setTab] = useState3("carte");
  const [menuOpen, setMenuOpen] = useState3(false);
  const [placing, setPlacing] = useState3(null);
  const [namePicker, setNamePicker] = useState3(null);
  const [firePicker, setFirePicker] = useState3(null);
  const [selected, setSelected] = useState3(null);
  const [now, setNow] = useState3(Date.now());
  // Thème clair/sombre : préférence personnelle d'affichage (pas une donnée du camp à
  // partager) — reste dans le localStorage de l'appareil, indépendamment de Firestore.
  const [lightMode, setLightMode] = useState3(() => {
    try { return localStorage.getItem("vigicamp-light-mode") === "1"; } catch (e) { return false; }
  });
  useEffect3(() => {
    try { localStorage.setItem("vigicamp-light-mode", lightMode ? "1" : "0"); } catch (e) {}
  }, [lightMode]);

  // Authentification Firebase — l'app entière attend que le SDK soit prêt (firebase-init.js,
  // chargé en <script type="module">, ne s'exécute pas forcément avant ce fichier) puis
  // affiche l'écran de connexion tant qu'aucun utilisateur n'est identifié.
  const [fbReady, setFbReady] = useState3(!!window.fb);
  const [user, setUser] = useState3(null);
  const [authLoading, setAuthLoading] = useState3(true);

  useEffect3(() => {
    if (window.fb) { setFbReady(true); return; }
    function onReady() { setFbReady(true); }
    window.addEventListener("firebase-ready", onReady);
    return () => window.removeEventListener("firebase-ready", onReady);
  }, []);

  useEffect3(() => {
    if (!fbReady) return;
    const unsub = window.fb.onAuthStateChanged(window.fb.auth, (u) => {
      setUser(u);
      setAuthLoading(false);
    });
    return unsub;
  }, [fbReady]);

  // Données partagées du camp, synchronisées en temps réel via Firestore (cf. hooks ci-dessus)
  // dès qu'un utilisateur est authentifié — les règles de sécurité Firestore exigent
  // `request.auth != null`, inutile de tenter de lire/écrire avant.
  const fsReady = fbReady && !!user;
  const [missions, setMissions, missionsLoaded] = useFirestoreCollection3("missions", fsReady);
  const [fires, setFires, firesLoaded] = useFirestoreCollection3("fires", fsReady);
  const [entries, setEntries, entriesLoaded] = useFirestoreCollection3("entries", fsReady, [["time", "desc"]]);
  const [roster, setRoster, rosterLoaded] = useFirestoreCollection3("roster", fsReady);
  const [vehicles, setVehicles, vehiclesLoaded] = useFirestoreCollection3("vehicles", fsReady);
  const [settings, setSettings, settingsLoaded] = useFirestoreDoc3(["meta", "settings"], fsReady, { redDay: false, presets: DEFAULT_PRESETS });
  const redDay = settings.redDay;
  const presets = settings.presets;
  function setRedDay(v) { setSettings((s) => ({ ...s, redDay: typeof v === "function" ? v(s.redDay) : v })); }
  function setPresets(v) { setSettings((s) => ({ ...s, presets: typeof v === "function" ? v(s.presets) : v })); }
  const loaded = missionsLoaded && firesLoaded && entriesLoaded && rosterLoaded && vehiclesLoaded && settingsLoaded;
  // Carroyage DFCI (même fichier que celui affiché sur la carte) chargé une fois ici pour
  // pouvoir nommer automatiquement un feu/fumée signalé selon la case.zone où il se trouve
  // (cf. findDfciGridRef) — indépendant de l'affichage de la grille sur la carte, qui peut
  // rester masqué.
  const [dfciGrid, setDfciGrid] = useState3(null);

  useEffect3(() => {
    const t = setInterval(() => setNow(Date.now()), 15000);
    return () => clearInterval(t);
  }, []);

  useEffect3(() => {
    fetch("js/data/dfci-grid.geojson")
      .then((r) => (r.ok ? r.json() : null))
      .then(setDfciGrid)
      .catch(() => {});
  }, []);

  // Amorçage initial de la base Firestore du camp (une seule fois, pour toute l'équipe — pas
  // par appareil comme l'ancien seed localStorage) : si personne n'a encore rien enregistré,
  // précharge l'effectif/parc de démonstration (cf. js/data/seed.js), déjà marqués "présents",
  // pour que l'outil soit tout de suite manipulable. `seededRef` évite de reproposer le seed
  // à chaque re-render une fois que le camp a réellement 0 personne/véhicule (journée finie).
  const seededRef = useRef3(false);
  useEffect3(() => {
    if (!loaded || seededRef.current) return;
    seededRef.current = true;
    if (roster.length === 0 && vehicles.length === 0 && entries.length === 0) {
      const seedRoster = (SEED_ROSTER || []).map((p) => ({ id: uid("p"), name: p.name, group: p.group || null }));
      const seedVehicles = (SEED_VEHICLES || []).map((v) => ({ id: uid("v"), name: v.name, plate: v.plate || null }));
      const seedTime = Date.now();
      const seedEntries = seedRoster.map((p) => ({ id: uid("e"), type: "entree", personId: p.id, names: p.name, motif: "", vehicleId: null, vehicule: "", time: seedTime, returnedAt: null }));
      setRoster(seedRoster);
      setVehicles(seedVehicles);
      setEntries(seedEntries);
    }
  }, [loaded]);

  const selectedMission = selected && selected.kind === "mission" ? missions.find((m) => m.id === selected.id) : null;
  const selectedFire = selected && selected.kind === "fire" ? fires.find((f) => f.id === selected.id) : null;
  // Missions "actives" (équipe pas encore entièrement rentrée) — celles affichées sur la
  // carte, dans "Missions en cours" et utilisables comme référence de triangulation. Les
  // missions terminées restent dans `missions` (donc dans le journal des missions, pour garder
  // une trace complète) mais disparaissent de ces trois endroits — cf. markPersonReturn /
  // returnMission, qui ne suppriment jamais la mission, ils la marquent juste `endedAt`.
  const activeMissions = missions.filter((m) => !m.endedAt);
  const vigies = activeMissions.filter((m) => m.type === "vigie");
  // Feux "actifs" (ni classés fausse alerte, ni éteints) — ceux qu'on affiche sur la carte et
  // dans "Feux en cours". Les autres restent dans `fires` (donc dans l'historique/le
  // stockage) mais disparaissent de ces deux endroits une fois leur statut passé à l'un ou
  // l'autre — cf. markFalseAlarm / markExtinguished, qui ne suppriment jamais le feu.
  const activeFires = fires.filter((f) => f.status !== "fausse_alerte" && f.status !== "eteint");

  function onMissionMoved(id, lon, lat) {
    setMissions((ms) => ms.map((m) => (m.id === id
      ? { ...m, x: lon, y: lat, log: [{ time: Date.now(), note: "Position mise à jour sur la carte" }, ...m.log].slice(0, 30) }
      : m)));
    setSelected({ kind: "mission", id });
  }
  function onFireMoved(id, lon, lat) {
    setFires((fs) => fs.map((f) => (f.id === id ? { ...f, x: lon, y: lat } : f)));
    setSelected({ kind: "fire", id });
  }

  function handleMapClickPlaceFire({ lon, lat }) {
    if (!placing) return;
    if (placing.kind === "fire") {
      const id = uid("f");
      setFires((fs) => [...fs, { id, x: lon, y: lat, status: "signale", time: Date.now(), log: [{ time: Date.now(), note: "Fumée signalée (position estimée)", status: "signale" }] }]);
      setSelected({ kind: "fire", id });
    }
    setPlacing(null);
  }

  // `vehicle`/`driverId` optionnels : seule l'entrée du conducteur désigné porte le véhicule
  // (même logique que pour une sortie individuelle — savoir qui conduisait en cas d'amende).
  function createMissionAtCamp(type, label, crew, vehicle, driverId) {
    const id = uid("m");
    const list = crew || [];
    setMissions((ms) => [
      ...ms,
      {
        id, type, label, x: CONFIG.CAMP_POSITION.lon, y: CONFIG.CAMP_POSITION.lat,
        status: "mouvement", lastContact: Date.now(), crew: list,
        log: [{ time: Date.now(), note: "Mission créée — départ du camp" }, ...list.map((c) => ({ time: Date.now(), note: `Départ du camp — ${c.name}` }))],
      },
    ]);
    if (list.length > 0) {
      setEntries((es) => [
        ...list.map((c) => {
          const isDriver = vehicle && c.personId === driverId;
          return {
            id: uid("e"), type: "sortie", personId: c.personId, names: c.name, motif: `Mission : ${label}`,
            vehicleId: isDriver ? vehicle.id : null, vehicule: isDriver ? vehicle.name : "",
            missionId: id, time: Date.now(), returnedAt: null,
          };
        }),
        ...es,
      ]);
    }
    setSelected({ kind: "mission", id });
  }

  // Un second clic sur le même bouton de type (Vigie/Patrouille/Barrière) referme le
  // panneau — évite de n'avoir que le petit bouton "Annuler" comme seule échappatoire.
  function openNamePicker(type) { setNamePicker((cur) => (cur && cur.type === type ? null : { type })); }
  function confirmPlacement(type, label, crew) { setNamePicker(null); createMissionAtCamp(type, label, crew); }
  function addPreset(type, name) {
    const trimmed = name.trim();
    if (!trimmed) return;
    setPresets((p) => (p[type].includes(trimmed) ? p : { ...p, [type]: [...p[type], trimmed] }));
  }
  function placeFireDirect() { setFirePicker(null); setPlacing({ kind: "fire" }); }
  function confirmTriangulatedFire(point, labelA, azA, labelB, azB) {
    const id = uid("f");
    setFires((fs) => [...fs, { id, x: point.lon, y: point.lat, status: "signale", time: Date.now(), log: [{ time: Date.now(), note: `Triangulé depuis ${labelA} (${azA}°) et ${labelB} (${azB}°)`, status: "signale" }] }]);
    setSelected({ kind: "fire", id });
    setFirePicker(null);
  }

  function logPersonMovement(person, type, motif, vehicle) {
    setEntries((es) => [{ id: uid("e"), type, personId: person.id, names: person.name, motif: motif || "", vehicleId: vehicle ? vehicle.id : null, vehicule: vehicle ? vehicle.name : "", time: Date.now(), returnedAt: null }, ...es]);
  }
  function markPersonReturn(personId) {
    setEntries((es) => {
      const openIdx = es.findIndex((e) => e.personId === personId && e.type === "sortie" && !e.returnedAt);
      if (openIdx === -1) return es;
      const copy = [...es];
      const entry = copy[openIdx];
      const returnTime = Date.now();
      copy[openIdx] = { ...entry, returnedAt: returnTime };
      // Nouveau mouvement "Entré" à part entière (en plus de l'annotation "Rentré HH:MM" sur
      // la sortie d'origine) — plus visible au premier coup d'œil dans le journal des
      // mouvements, exactement comme une arrivée classique (même couleur/icône verte).
      const returnEntry = { id: uid("e"), type: "entree", personId, names: entry.names, motif: entry.motif, vehicleId: entry.vehicleId, vehicule: entry.vehicule, missionId: entry.missionId || null, time: returnTime, returnedAt: null };
      if (entry.missionId) {
        // La mission se termine automatiquement dès que son dernier équipier est rentré — que
        // ce soit via un retour individuel, "Faire rentrer le groupe" ou "Faire rentrer la
        // mission" (qui appellent tous cette fonction personne par personne). Elle n'est
        // jamais supprimée de `missions` (juste marquée `endedAt`) pour garder une trace
        // complète dans le journal des missions — seul son affichage actif (carte, "Missions
        // en cours") en tient compte, via `activeMissions`.
        setMissions((ms) => {
          let justEndedId = null;
          const updated = ms.map((m) => {
            if (m.id !== entry.missionId) return m;
            const newCrew = (m.crew || []).filter((c) => c.personId !== personId);
            if (newCrew.length === 0 && !m.endedAt) {
              justEndedId = m.id;
              return { ...m, crew: newCrew, endedAt: returnTime, log: [{ time: returnTime, note: "Mission terminée — équipe rentrée" }, ...m.log] };
            }
            return { ...m, crew: newCrew };
          });
          if (justEndedId) {
            setSelected((sel) => (sel && sel.kind === "mission" && sel.id === justEndedId ? null : sel));
          }
          return updated;
        });
      }
      return [returnEntry, ...copy];
    });
  }

  function pointMission(id) {
    setMissions((ms) => ms.map((m) => (m.id === id
      ? { ...m, lastContact: Date.now(), status: "position", log: [{ time: Date.now(), note: "Pointage radio" }, ...m.log].slice(0, 30) }
      : m)));
  }
  function setMissionStatus(id, status) {
    setMissions((ms) => ms.map((m) => (m.id === id
      ? { ...m, status, lastContact: Date.now(), log: [{ time: Date.now(), note: `Statut : ${STATUS[status].label}` }, ...m.log].slice(0, 30) }
      : m)));
  }
  // "Faire rentrer la mission" — réutilise markPersonReturn personne par personne (même
  // mécanique que "Faire rentrer le groupe" côté Effectif) : chacun retrouve un mouvement
  // "Entré" dans le journal, et la mission se termine (sans être supprimée) dès le dernier
  // retour, via la logique déjà présente dans markPersonReturn.
  function returnMission(id) {
    const m = missions.find((mm) => mm.id === id);
    if (m) (m.crew || []).forEach((c) => markPersonReturn(c.personId));
  }

  // Tunnel d'étapes du signalement d'un feu/fumée — une seule action possible à la fois selon
  // l'étape en cours (FirePanel n'affiche plus une liste de statuts cliquables au choix) :
  // Signalé → [Enregistrer] → Enregistré → [Fausse alerte | Confirmer le départ de feu] →
  // Confirmé → [Pompiers informés] → Pompiers informés → [Feu éteint]. "Fausse alerte" et
  // "Feu éteint" ne suppriment pas le feu (voir markFalseAlarm/markExtinguished plus bas) :
  // ils le retirent seulement de la carte et de "Feux en cours", en gardant son historique.

  // Signalé → Enregistré : résume dans l'historique tout ce qui a été renseigné dans la fiche
  // de reconnaissance — celle-ci n'est plus modifiable/affichée une fois enregistrée.
  function registerFireInfo(id) {
    setFires((fs) => fs.map((f) => {
      if (f.id !== id) return f;
      const r = f.recon || {};
      const parts = [];
      if (r.environnement) parts.push(r.environnement);
      if (r.menace && r.menace.length > 0) parts.push(`Menace : ${r.menace.join(", ")}`);
      if (r.couleurFumee) parts.push(`Fumée ${r.couleurFumee.toLowerCase()}`);
      if (r.baseFlammesVisible) parts.push(`Base des flammes visible : ${r.baseFlammesVisible}`);
      if (r.formeFumee) parts.push(r.formeFumee);
      if (r.inclinaison) parts.push(r.inclinaison);
      if (r.direction) parts.push(`Direction ${r.direction}`);
      if (r.hauteur) parts.push(r.hauteur);
      const newLog = [{ time: Date.now(), note: "Statut : Enregistré", status: "enregistre" }];
      if (parts.length > 0) newLog.push({ time: Date.now(), note: `Reconnaissance : ${parts.join(" · ")}`, status: "enregistre" });
      return { ...f, status: "enregistre", log: [...newLog, ...f.log] };
    }));
  }
  // Enregistré → Confirmé (bouton "Confirmer le départ de feu" — l'autre choix à cette étape,
  // "Fausse alerte", est géré par markFalseAlarm ci-dessous).
  function confirmFireStart(id) {
    setFires((fs) => fs.map((f) => (f.id === id ? { ...f, status: "confirme", log: [{ time: Date.now(), note: "Statut : Confirmé — départ de feu confirmé", status: "confirme" }, ...f.log] } : f)));
  }
  // Confirmé → Pompiers informés.
  function markFirefightersInformed(id) {
    setFires((fs) => fs.map((f) => (f.id === id ? { ...f, status: "pompiers", log: [{ time: Date.now(), note: "Statut : Pompiers informés", status: "pompiers" }, ...f.log] } : f)));
  }
  // "Fausse alerte" et "Feu éteint" ne suppriment PAS le feu de l'état — l'historique (toutes
  // les informations relevées et toutes les actions du PC) doit rester consultable. Seul leur
  // statut change ; c'est ce statut qui les fait disparaître de la carte et de "Feux en cours"
  // (cf. le filtre sur `fires` passé à MapView/FiresPanel), pas une suppression réelle.
  //
  // Chaque entrée de log porte son propre `status` (celui du moment), plutôt que de laisser
  // l'affichage utiliser le statut ACTUEL du feu pour toute l'historique — sinon, une fois le
  // feu classé fausse alerte/éteint, tous ses points colorés dans "Activité récente"
  // reprendraient rétroactivement cette même couleur finale au lieu de garder celle de chaque
  // étape effectivement traversée.
  function markFalseAlarm(id) {
    setFires((fs) => fs.map((f) => (f.id === id ? { ...f, status: "fausse_alerte", log: [{ time: Date.now(), note: "Statut : Fausse alerte", status: "fausse_alerte" }, ...f.log] } : f)));
    if (selected && selected.kind === "fire" && selected.id === id) setSelected(null);
  }
  function markExtinguished(id) {
    setFires((fs) => fs.map((f) => (f.id === id ? { ...f, status: "eteint", log: [{ time: Date.now(), note: "Statut : Éteint", status: "eteint" }, ...f.log] } : f)));
    if (selected && selected.kind === "fire" && selected.id === id) setSelected(null);
  }
  // Fiche de reconnaissance (environnement, menace, aspect de la fumée...) — un simple objet
  // fusionné au fil des réponses, pas d'historique/log dédié (contrairement au statut).
  function updateFireRecon(id, patch) {
    setFires((fs) => fs.map((f) => (f.id === id ? { ...f, recon: { ...f.recon, ...patch } } : f)));
  }
  // Historique complet (non plafonné) de tout ce qui s'est passé dans l'onglet Carte &
  // missions — missions ET feux/fumées — pour la colonne "Journal des missions" de l'onglet
  // Journal. "Activité récente" (ActivityFeed) en est un simple aperçu tronqué aux 10 plus
  // récents.
  const missionsJournal = useMemo3(() => {
    const items = [];
    missions.forEach((m) => m.log.forEach((l) => items.push({ time: l.time, text: `${MISSION_TYPES[m.type].label} ${m.label} — ${l.note}`, color: MISSION_TYPES[m.type].color })));
    fires.forEach((f) => f.log.forEach((l) => items.push({ time: l.time, text: `Feu — ${l.note}`, color: FIRE_COLOR[l.status || f.status] })));
    return items.sort((a, b) => b.time - a.time);
  }, [missions, fires]);
  const activity = useMemo3(() => missionsJournal.slice(0, 10), [missionsJournal]);

  if (!fbReady || authLoading) {
    return (
      <div style={{ display: "flex", alignItems: "center", justifyContent: "center", height: "100vh", color: "#8b9aa3", fontFamily: "ui-sans-serif, system-ui, sans-serif", fontSize: 13 }}>
        Chargement de VigiCamp…
      </div>
    );
  }
  if (!user) {
    return <LoginScreen onLogin={(email, password) => window.fb.signInWithEmailAndPassword(window.fb.auth, email, password)} />;
  }
  if (!loaded) {
    return (
      <div style={{ display: "flex", alignItems: "center", justifyContent: "center", height: "100vh", color: "#8b9aa3", fontFamily: "ui-sans-serif, system-ui, sans-serif", fontSize: 13 }}>
        Synchronisation des données du camp…
      </div>
    );
  }

  return (
    <div style={{
      fontFamily: "ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif",
      background: "var(--bg0)", color: "var(--text)", height: "100vh", transition: "background 0.4s",
      display: "flex", flexDirection: "column", overflow: "hidden",
      "--bg0": lightMode ? "#eef1f3" : "#0f1417",
      "--bg1": lightMode ? "#ffffff" : "#131a1e",
      "--bg2": lightMode ? "#ffffff" : "#161d22",
      "--bg3": lightMode ? "#f3f5f6" : "#0f1417",
      "--bg4": lightMode ? "#e6eaec" : "#12181c",
      "--bg2-red": lightMode ? "#fef2f2" : "#211416",
      "--border": lightMode ? "#d7dde1" : "#2a343a",
      "--text": lightMode ? "#1a2226" : "#e8edf0",
      "--text-muted": lightMode ? "#57646d" : "#8b9aa3",
      "--text-faint": lightMode ? "#7c8790" : "#5f6b72",
    }}>
      <Header redDay={redDay} now={now} menuOpen={menuOpen} setMenuOpen={setMenuOpen} lightMode={lightMode} setLightMode={setLightMode} setTab={setTab} />
      <Tabs tab={tab} setTab={setTab} />

      <div style={{ flex: "1 1 auto", minHeight: 0, display: "flex", flexDirection: "column" }}>
        {/* Toujours montée (display:none plutôt que démontage conditionnel) : la carte
            MapLibre garde ainsi sa position/zoom/inclinaison en changeant d'onglet, au lieu
            de se réinitialiser à chaque fois qu'on revient sur "Carte & missions". */}
          <div className="carte-row" style={{ flex: "1 1 auto", minHeight: 0, display: tab === "carte" ? "flex" : "none", gap: 12, padding: "12px 16px" }}>
            <div className="carte-sidebar" style={{ flex: "0 0 340px", minWidth: 300, display: "flex", flexDirection: "column", minHeight: 0 }}>
              <div className="carte-sidebar-scroll" style={{ flex: "1 1 auto", minHeight: 0, display: "flex", flexDirection: "column", gap: 12, overflowY: "auto" }}>
                {selectedMission && (
                  <MissionPanel mission={selectedMission} now={now}
                    onPoint={() => pointMission(selectedMission.id)}
                    onStatus={(s) => setMissionStatus(selectedMission.id, s)}
                    onReturnMission={() => returnMission(selectedMission.id)}
                    onClose={() => setSelected(null)} />
                )}
                {selectedFire && (
                  <FirePanel fire={selectedFire}
                    onRegister={() => registerFireInfo(selectedFire.id)}
                    onConfirmFire={() => confirmFireStart(selectedFire.id)}
                    onFirefightersInformed={() => markFirefightersInformed(selectedFire.id)}
                    onFalseAlarm={() => markFalseAlarm(selectedFire.id)}
                    onExtinguish={() => markExtinguished(selectedFire.id)}
                    onUpdateRecon={updateFireRecon}
                    dfciGrid={dfciGrid}
                    onClose={() => setSelected(null)} />
                )}
                <RoutinePanel missions={activeMissions} now={now} onPoint={pointMission} onSelect={(id) => setSelected({ kind: "mission", id })} />
                <FiresPanel fires={activeFires} now={now} onSelect={(id) => setSelected({ kind: "fire", id })} dfciGrid={dfciGrid} />
                <ActivityFeed activity={activity} now={now} />
              </div>
              <WeatherPanel />
              <TipsPanel />
            </div>
            <div className="carte-map-col" style={{ flex: "1 1 640px", minWidth: 320, display: "flex", flexDirection: "column", minHeight: 0 }}>
              <Toolbar
                placing={placing} setPlacing={setPlacing} openNamePicker={openNamePicker}
                namePicker={namePicker} setNamePicker={setNamePicker}
                presets={presets} addPreset={addPreset}
                confirmPlacement={confirmPlacement}
                firePicker={firePicker} setFirePicker={setFirePicker}
                vigies={vigies} placeFireDirect={placeFireDirect}
                confirmTriangulatedFire={confirmTriangulatedFire}
                roster={roster} entries={entries}
              />
              <div className="carte-map-canvas-wrap" style={{ flex: "1 1 auto", minHeight: 0 }}>
                <MapView
                  missions={activeMissions} fires={activeFires} selected={selected} onSelect={setSelected}
                  placing={placing} onMapClickPlaceFire={handleMapClickPlaceFire}
                  onMissionMoved={onMissionMoved} onFireMoved={onFireMoved}
                  firePicker={firePicker}
                />
              </div>
            </div>
          </div>

        {tab === "effectif" && (
          <div style={{ flex: "1 1 auto", minHeight: 0, overflowY: "auto" }}>
            <EffectifTab roster={roster} setRoster={setRoster} entries={entries} setEntries={setEntries}
              onArrival={(p) => logPersonMovement(p, "entree")}
              onSortie={(p, motif, vehicle) => logPersonMovement(p, "sortie", motif, vehicle)}
              onReturn={(p) => markPersonReturn(p.id)}
              onCreateMission={createMissionAtCamp}
              vehicles={vehicles} setVehicles={setVehicles} missions={missions} presets={presets} />
          </div>
        )}

        {tab === "journal" && (
          <div style={{ flex: "1 1 auto", minHeight: 0, overflowY: "auto" }}>
            <EntriesTab entries={entries} missions={missions} missionsJournal={missionsJournal} />
          </div>
        )}

        {tab === "settings" && (
          <div style={{ flex: "1 1 auto", minHeight: 0, overflowY: "auto" }}>
            <SettingsPage redDay={redDay} setRedDay={setRedDay} setTab={setTab} />
          </div>
        )}
      </div>
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
