<?php
/* ============================================================
   HERBIER — application + API de données, en un seul fichier.
   Les données sont stockées dans data.json, à côté de ce fichier.

   >>> CHANGEZ LE MOT DE PASSE CI-DESSOUS AVANT DE METTRE EN LIGNE <<<
   ============================================================ */
session_start();

$PASSWORD  = 'change-me';
$DATA_FILE = __DIR__ . '/data.json';

/* ---------- Connexion / déconnexion ---------- */
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['password'])) {
    if (hash_equals($PASSWORD, (string) $_POST['password'])) {
        $_SESSION['herbier_auth'] = true;
    } else {
        $_SESSION['herbier_error'] = true;
    }
    header('Location: ' . strtok($_SERVER['REQUEST_URI'], '?'));
    exit;
}
if (isset($_GET['logout'])) {
    session_destroy();
    header('Location: ' . strtok($_SERVER['REQUEST_URI'], '?'));
    exit;
}
$isAuth = !empty($_SESSION['herbier_auth']);

/* ---------- API : lecture / écriture des données ---------- */
if (isset($_GET['api']) && $_GET['api'] === 'data') {
    header('Content-Type: application/json; charset=utf-8');

    if (!$isAuth) {
        http_response_code(401);
        echo json_encode(['error' => 'unauthorized']);
        exit;
    }

    if ($_SERVER['REQUEST_METHOD'] === 'GET') {
        if (file_exists($DATA_FILE)) {
            echo file_get_contents($DATA_FILE);
        } else {
            echo json_encode(['families' => []]);
        }
        exit;
    }

    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $raw     = file_get_contents('php://input');
        $decoded = json_decode($raw, true);
        if (!is_array($decoded) || !isset($decoded['families']) || !is_array($decoded['families'])) {
            http_response_code(400);
            echo json_encode(['error' => 'invalid payload']);
            exit;
        }
        $ok = @file_put_contents($DATA_FILE, json_encode($decoded, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
        if ($ok === false) {
            http_response_code(500);
            echo json_encode(['error' => "écriture impossible : vérifiez les droits d'écriture du dossier"]);
            exit;
        }
        echo json_encode(['ok' => true]);
        exit;
    }

    http_response_code(405);
    echo json_encode(['error' => 'method not allowed']);
    exit;
}

/* ---------- Page de connexion ---------- */
if (!$isAuth) {
    $loginError = !empty($_SESSION['herbier_error']);
    unset($_SESSION['herbier_error']);
    ?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>Herbier — connexion</title>
<style>
  :root{ --paper:#F1ECDE; --pine:#2F4B3C; --pine-dark:#1F3329; --ink:#1E2A20; --ink-soft:#4B5A4E; --line:rgba(30,42,32,.18); --card-bg:#FBF8EE; --danger:#A1442E; --serif:"Iowan Old Style","Palatino Linotype","Book Antiqua",Palatino,Georgia,serif; --sans:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif; }
  *{box-sizing:border-box;}
  body{margin:0; min-height:100vh; display:flex; align-items:center; justify-content:center; background:var(--paper); font-family:var(--sans); color:var(--ink); padding:20px;}
  .card{background:var(--card-bg); border:1px solid var(--line); border-radius:16px; padding:28px 24px; max-width:340px; width:100%; box-shadow:0 4px 16px rgba(30,42,32,.14);}
  .brand{font-family:var(--serif); font-size:22px; color:var(--pine-dark); text-align:center; margin-bottom:4px;}
  .sub{text-align:center; color:var(--ink-soft); font-size:13px; margin:0 0 20px;}
  input{width:100%; border:1px solid var(--line); background:#fff; border-radius:10px; padding:12px; font-size:15px; margin-bottom:12px; font-family:var(--sans);}
  button{width:100%; background:var(--pine); color:var(--paper); border:none; border-radius:10px; padding:12px; font-size:15px; font-weight:600; cursor:pointer;}
  button:active{background:var(--pine-dark);}
  .error{color:var(--danger); font-size:13px; text-align:center; margin:-6px 0 14px;}
</style>
</head>
<body>
  <form class="card" method="post">
    <div class="brand">🌿 Herbier</div>
    <p class="sub">Répertoire botanique privé</p>
    <?php if ($loginError): ?><p class="error">Mot de passe incorrect.</p><?php endif; ?>
    <input type="password" name="password" placeholder="Mot de passe" autofocus required>
    <button type="submit">Entrer</button>
  </form>
</body>
</html>
    <?php
    exit;
}
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, viewport-fit=cover">
<title>Herbier — répertoire botanique</title>
<style>
  :root{
    --paper:#F1ECDE;
    --paper-2:#E7DFC8;
    --ink:#1E2A20;
    --ink-soft:#4B5A4E;
    --pine:#2F4B3C;
    --pine-dark:#1F3329;
    --sage:#7C9473;
    --rust:#8B5A3C;
    --gold:#A9822F;
    --line:rgba(30,42,32,.18);
    --card-bg:#FBF8EE;
    --danger:#A1442E;
    --radius:14px;
    --shadow:0 2px 10px rgba(30,42,32,.14);
    --serif: "Iowan Old Style","Palatino Linotype","Book Antiqua",Palatino,Georgia,serif;
    --sans: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  }
  *{box-sizing:border-box; -webkit-tap-highlight-color:transparent;}
  html,body{height:100%;}
  body{
    margin:0; background:var(--paper);
    background-image:
      radial-gradient(circle at 20% 10%, rgba(124,148,115,.10), transparent 40%),
      radial-gradient(circle at 90% 80%, rgba(139,90,60,.08), transparent 45%);
    color:var(--ink); font-family:var(--sans);
    -webkit-font-smoothing:antialiased;
  }
  #app{max-width:640px; margin:0 auto; min-height:100vh; display:flex; flex-direction:column; position:relative;}

  header.topbar{
    position:sticky; top:0; z-index:20;
    background:var(--paper); border-bottom:1px solid var(--line);
    padding:env(safe-area-inset-top) 14px 0 14px;
  }
  .topbar-row{display:flex; align-items:center; gap:10px; padding:10px 0 8px;}
  .brand{display:flex; align-items:center; gap:8px; font-family:var(--serif); font-size:19px; color:var(--pine-dark); font-weight:600; letter-spacing:.2px;}
  .brand svg{width:22px; height:22px; flex:none;}
  .sync-status{font-size:11px; color:var(--ink-soft); margin-left:auto; white-space:nowrap;}
  .sync-status.saving{color:var(--gold);}
  .sync-status.error{color:var(--danger); font-weight:600;}
  .menu-btn{background:none; border:none; color:var(--pine-dark); font-size:20px; padding:6px 4px; cursor:pointer; line-height:1;}
  .breadcrumb{display:flex; flex-wrap:wrap; align-items:center; gap:2px; font-size:12.5px; color:var(--ink-soft); padding-bottom:8px; min-height:18px;}
  .breadcrumb button{background:none;border:none;color:var(--ink-soft);font:inherit;padding:2px 3px;cursor:pointer;text-decoration:underline; text-decoration-color:var(--line);}
  .breadcrumb button.current{color:var(--pine-dark); font-weight:600; text-decoration:none;}
  .breadcrumb .sep{color:var(--line); padding:0 1px;}
  .search-wrap{padding:0 0 12px;}
  .search-wrap input{
    width:100%; border:1px solid var(--line); background:var(--card-bg);
    border-radius:10px; padding:10px 12px; font-size:15px; font-family:var(--sans);
    color:var(--ink); outline:none;
  }
  .search-wrap input:focus{border-color:var(--sage);}

  main#content{flex:1; padding:14px 14px 100px;}
  .eyebrow{
    text-transform:uppercase; letter-spacing:1.4px; font-size:11px; color:var(--sage);
    font-weight:700; margin:2px 0 10px;
  }
  h1.title{font-family:var(--serif); font-size:24px; margin:0 0 4px; color:var(--pine-dark);}
  p.sub{color:var(--ink-soft); font-size:13.5px; margin:0 0 16px;}

  .list{list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:9px;}
  .row{
    display:flex; align-items:center; gap:12px; background:var(--card-bg);
    border:1px solid var(--line); border-left:4px solid var(--pine);
    border-radius:var(--radius); padding:13px 12px; box-shadow:var(--shadow);
    cursor:pointer;
  }
  .row.genus{border-left-color:var(--sage);}
  .row.species{border-left-color:var(--rust); border-left-style:dashed;}
  .row .row-main{flex:1; min-width:0;}
  .row .row-title{font-family:var(--serif); font-size:16.5px; color:var(--ink); font-weight:600;}
  .row .row-title.italic{font-style:italic;}
  .row .row-count{font-size:12px; color:var(--ink-soft); margin-top:2px;}
  .row .chevron{color:var(--line); font-size:20px; flex:none;}
  .row .trash{
    flex:none; background:none; border:none; color:var(--ink-soft); opacity:.55;
    font-size:16px; padding:6px; cursor:pointer;
  }
  .row .trash:active{opacity:1; color:var(--danger);}

  .empty{ text-align:center; padding:50px 20px; color:var(--ink-soft); }
  .empty .sprig{width:54px; height:54px; margin:0 auto 14px; opacity:.65;}
  .empty p{margin:0 0 18px; font-size:14.5px; line-height:1.5;}

  .btn{
    display:inline-flex; align-items:center; justify-content:center; gap:6px;
    font-family:var(--sans); font-size:14.5px; font-weight:600; border-radius:10px;
    padding:11px 18px; border:none; cursor:pointer;
  }
  .btn-primary{background:var(--pine); color:var(--paper);}
  .btn-primary:active{background:var(--pine-dark);}
  .btn-ghost{background:transparent; color:var(--pine-dark); border:1px solid var(--line);}
  .btn-danger{background:transparent; color:var(--danger); border:1px solid rgba(161,68,46,.35);}
  .btn-block{width:100%;}

  .detail-card{background:var(--card-bg); border:1px solid var(--line); border-radius:var(--radius); padding:20px 18px; box-shadow:var(--shadow); margin-bottom:16px;}
  .sprig-divider{width:100%; height:26px; display:flex; align-items:center; justify-content:center; margin-bottom:10px; opacity:.7;}
  .sprig-divider svg{height:100%;}
  .binomial{font-family:var(--serif); font-style:italic; font-size:23px; color:var(--pine-dark); text-align:center; margin:0;}
  .common-name{text-align:center; color:var(--ink-soft); font-size:14px; margin:4px 0 0;}
  .family-tag{
    display:block; text-align:center; margin-top:10px; font-size:11px; letter-spacing:1px;
    text-transform:uppercase; color:var(--sage); font-weight:700;
  }

  .section-label{
    font-size:11.5px; text-transform:uppercase; letter-spacing:1.2px; color:var(--ink-soft);
    font-weight:700; margin:20px 0 8px;
  }
  .description-box{ font-size:14.5px; line-height:1.6; color:var(--ink); white-space:pre-wrap; }
  .description-box.placeholder{color:var(--ink-soft); font-style:italic;}
  .link-btn{background:none; border:none; color:var(--pine); font-weight:600; font-size:13.5px; padding:4px 0; cursor:pointer; text-decoration:underline;}

  .note-card{ background:var(--paper-2); border-radius:10px; padding:10px 12px; margin-bottom:8px; position:relative; }
  .note-date{font-size:11px; color:var(--gold); font-weight:700; letter-spacing:.3px;}
  .note-text{font-size:14px; margin-top:3px; white-space:pre-wrap; line-height:1.5;}
  .note-del{position:absolute; top:8px; right:8px; background:none; border:none; color:var(--ink-soft); opacity:.5; cursor:pointer; font-size:13px;}

  .danger-zone{margin-top:26px; padding-top:14px; border-top:1px dashed var(--line); text-align:center;}

  .fab{
    position:fixed; right:calc(50% - 320px + 20px); bottom:26px; z-index:30;
    width:56px; height:56px; border-radius:50%; background:var(--pine); color:var(--paper);
    border:none; font-size:28px; box-shadow:0 6px 16px rgba(31,51,41,.35); cursor:pointer;
    display:flex; align-items:center; justify-content:center; line-height:1;
  }
  @media (max-width:680px){ .fab{ right:20px; } }
  .fab:active{background:var(--pine-dark);}
  .fab.hidden{display:none;}

  .modal-overlay{
    position:fixed; inset:0; background:rgba(20,28,22,.45); z-index:50;
    display:flex; align-items:flex-end; justify-content:center;
  }
  .modal-overlay.hidden{display:none;}
  .modal-sheet{
    background:var(--paper); width:100%; max-width:640px; border-radius:20px 20px 0 0;
    padding:20px 20px calc(24px + env(safe-area-inset-bottom)); max-height:88vh; overflow-y:auto;
    box-shadow:0 -8px 24px rgba(0,0,0,.2);
  }
  .modal-handle{width:38px; height:4px; background:var(--line); border-radius:3px; margin:0 auto 16px;}
  .modal-sheet h2{font-family:var(--serif); font-size:19px; margin:0 0 16px; color:var(--pine-dark);}
  .field{margin-bottom:14px;}
  .field label{display:block; font-size:12px; text-transform:uppercase; letter-spacing:.8px; color:var(--ink-soft); font-weight:700; margin-bottom:6px;}
  .field input, .field textarea{
    width:100%; border:1px solid var(--line); background:var(--card-bg); border-radius:10px;
    padding:11px 12px; font-size:15px; font-family:var(--sans); color:var(--ink); outline:none; resize:vertical;
  }
  .field input:focus, .field textarea:focus{border-color:var(--sage);}
  .field-hint{font-size:11.5px; color:var(--ink-soft); margin-top:4px;}
  .modal-actions{display:flex; gap:10px; margin-top:8px;}
  .modal-actions .btn{flex:1;}
  .action-list{display:flex; flex-direction:column; gap:8px;}
  .action-list button, .action-list a{
    width:100%; text-align:left; background:var(--card-bg); border:1px solid var(--line);
    border-radius:10px; padding:13px 14px; font-size:15px; font-family:var(--sans);
    color:var(--ink); cursor:pointer; display:block; text-decoration:none; box-sizing:border-box;
  }
  .action-list button.danger{color:var(--danger);}

  .toast{
    position:fixed; bottom:96px; left:50%; transform:translateX(-50%);
    background:var(--pine-dark); color:var(--paper); padding:10px 18px; border-radius:20px;
    font-size:13.5px; z-index:60; box-shadow:0 4px 12px rgba(0,0,0,.25);
  }
  .toast.hidden{display:none;}
</style>
</head>
<body>
<div id="app">
  <header class="topbar">
    <div class="topbar-row">
      <div class="brand">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="M12 21V8"/><path d="M12 12c0-4 3-6 7-6 0 4-2 7-7 7"/><path d="M12 15c0-3-2.5-5-6-5 0 3.2 2 5.6 6 5.8"/></svg>
        Herbier
      </div>
      <span class="sync-status" id="syncStatus"></span>
      <button class="menu-btn" data-action="open-menu" aria-label="Menu">⋯</button>
    </div>
    <div id="breadcrumb" class="breadcrumb"></div>
    <div class="search-wrap">
      <input id="searchInput" type="text" placeholder="Rechercher une plante croisée…" autocomplete="off">
    </div>
  </header>

  <main id="content"></main>

  <button class="fab hidden" id="fab" aria-label="Ajouter">+</button>

  <div class="modal-overlay hidden" id="modalOverlay">
    <div class="modal-sheet" id="modalSheet"></div>
  </div>

  <div class="toast hidden" id="toast"></div>
  <input type="file" id="importFile" accept="application/json" style="display:none">
</div>

<script>
(function(){
  "use strict";

  /* ---------- DATA ---------- */
  var API_URL = location.pathname + "?api=data";
  var db = { families: [] };
  var nav = { screen:"families", familyId:null, genusId:null, speciesId:null };
  var searchQuery = "";

  function uid(){ return "id" + Date.now().toString(36) + Math.random().toString(36).slice(2,7); }
  function esc(s){
    return String(s==null?"":s).replace(/[&<>"']/g, function(c){
      return {"&":"&amp;","<":"&lt;",">":"&gt;","\"":"&quot;","'":"&#39;"}[c];
    });
  }
  function plural(n, one, many){ return n + " " + (n<=1 ? one : many); }
  function sortByName(arr){ return arr.slice().sort(function(a,b){ return a.name.localeCompare(b.name,"fr",{sensitivity:"base"}); }); }
  function today(){
    var d = new Date();
    return d.toLocaleDateString("fr-FR", { day:"2-digit", month:"short", year:"numeric" });
  }

  function getFamily(id){ return db.families.find(function(f){ return f.id===id; }); }
  function getGenus(fam, id){ return fam ? fam.genera.find(function(g){ return g.id===id; }) : null; }
  function getSpecies(gen, id){ return gen ? gen.species.find(function(s){ return s.id===id; }) : null; }

  /* ---------- SERVEUR : lecture / écriture ---------- */
  function fetchDB(){
    return fetch(API_URL, { credentials:"same-origin" })
      .then(function(r){ if(!r.ok) throw new Error("http "+r.status); return r.json(); })
      .then(function(data){
        db = (data && Array.isArray(data.families)) ? data : { families: [] };
      });
  }

  var saveChain = Promise.resolve();
  var pendingSaves = 0;
  function saveDB(){
    pendingSaves++;
    setSyncStatus("saving");
    saveChain = saveChain.then(function(){
      return fetch(API_URL, {
        method:"POST",
        credentials:"same-origin",
        headers:{ "Content-Type":"application/json" },
        body: JSON.stringify(db)
      });
    }).then(function(r){
      pendingSaves = Math.max(0, pendingSaves-1);
      if(!r.ok) throw new Error("http "+r.status);
      if(pendingSaves===0) setSyncStatus("saved");
    }).catch(function(){
      pendingSaves = Math.max(0, pendingSaves-1);
      setSyncStatus("error");
      toast("⚠ Échec de l'enregistrement — vérifiez votre connexion");
    });
  }
  function setSyncStatus(state){
    var el = document.getElementById("syncStatus");
    if(!el) return;
    if(state==="saving"){ el.textContent="Enregistrement…"; el.className="sync-status saving"; }
    else if(state==="saved"){
      el.textContent="Enregistré ✓"; el.className="sync-status saved";
      setTimeout(function(){ if(el.textContent==="Enregistré ✓") el.textContent=""; }, 1500);
    }
    else if(state==="error"){ el.textContent="Non enregistré ⚠"; el.className="sync-status error"; }
  }

  /* ---------- MUTATIONS ---------- */
  function addFamily(name){
    db.families.push({ id: uid(), name: name.trim(), genera: [] });
    saveDB();
  }
  function addGenus(familyId, name){
    var fam = getFamily(familyId);
    if(!fam) return;
    fam.genera.push({ id: uid(), name: name.trim(), species: [] });
    saveDB();
  }
  function addSpecies(familyId, genusId, epithet, commonName, description){
    var fam = getFamily(familyId), gen = getGenus(fam, genusId);
    if(!gen) return;
    gen.species.push({ id: uid(), epithet: epithet.trim(), commonName: (commonName||"").trim(), description: (description||"").trim(), notes: [] });
    saveDB();
  }
  function updateDescription(familyId, genusId, speciesId, text){
    var fam = getFamily(familyId), gen = getGenus(fam, genusId), sp = getSpecies(gen, speciesId);
    if(!sp) return;
    sp.description = text.trim();
    saveDB();
  }
  function addNote(familyId, genusId, speciesId, text){
    var fam = getFamily(familyId), gen = getGenus(fam, genusId), sp = getSpecies(gen, speciesId);
    if(!sp) return;
    sp.notes.unshift({ id: uid(), date: today(), text: text.trim() });
    saveDB();
  }
  function deleteFamily(id){ db.families = db.families.filter(function(f){ return f.id!==id; }); saveDB(); }
  function deleteGenus(fam, id){ fam.genera = fam.genera.filter(function(g){ return g.id!==id; }); saveDB(); }
  function deleteSpecies(gen, id){ gen.species = gen.species.filter(function(s){ return s.id!==id; }); saveDB(); }
  function deleteNote(sp, id){ sp.notes = sp.notes.filter(function(n){ return n.id!==id; }); saveDB(); }

  /* ---------- TOAST ---------- */
  var toastTimer = null;
  function toast(msg){
    var el = document.getElementById("toast");
    el.textContent = msg;
    el.classList.remove("hidden");
    clearTimeout(toastTimer);
    toastTimer = setTimeout(function(){ el.classList.add("hidden"); }, 1700);
  }

  /* ---------- ICONS ---------- */
  function sprigSVG(){
    return '<svg viewBox="0 0 120 26" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" fill="none" stroke-width="1.4" stroke-linecap="round" style="color:var(--sage)">'
      + '<path d="M2 13 H118"/>'
      + '<path d="M60 13 C60 6 55 3 50 2 C52 8 55 11 60 13"/>'
      + '<path d="M60 13 C60 6 65 3 70 2 C68 8 65 11 60 13"/>'
      + '<path d="M42 13 C42 8 38 6 34 6 C36 10 38 12 42 13"/>'
      + '<path d="M78 13 C78 8 82 6 86 6 C84 10 82 12 78 13"/>'
      + '<circle cx="60" cy="13" r="1.8" fill="currentColor" stroke="none"/>'
      + '</svg>';
  }
  function emptySprigSVG(){
    return '<svg class="sprig" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" fill="none" stroke-width="1.6" stroke-linecap="round">'
      + '<path d="M32 58V22"/><path d="M32 30c0-10 8-16 18-16 0 10-6 18-18 18"/>'
      + '<path d="M32 38c0-8-7-13-16-13 0 8 6 14 16 14.5"/></svg>';
  }

  /* ---------- RENDER ---------- */
  function render(){
    renderBreadcrumb();
    var content = document.getElementById("content");
    var fab = document.getElementById("fab");

    if(searchQuery){
      content.innerHTML = renderSearchResults();
      fab.classList.add("hidden");
      return;
    }
    fab.classList.remove("hidden");

    if(nav.screen==="families") content.innerHTML = renderFamilies();
    else if(nav.screen==="genera") content.innerHTML = renderGenera();
    else if(nav.screen==="species") content.innerHTML = renderSpeciesList();
    else if(nav.screen==="detail") content.innerHTML = renderDetail();
  }

  function renderBreadcrumb(){
    var bc = document.getElementById("breadcrumb");
    var parts = [];
    parts.push({ label:"Herbier", action:"go-families", current: nav.screen==="families" && !searchQuery });

    if(searchQuery){
      bc.innerHTML = '<span>Résultats pour «\u00A0' + esc(searchQuery) + '\u00A0»</span>';
      return;
    }

    var fam = nav.familyId ? getFamily(nav.familyId) : null;
    var gen = fam && nav.genusId ? getGenus(fam, nav.genusId) : null;
    var sp = gen && nav.speciesId ? getSpecies(gen, nav.speciesId) : null;

    if(fam) parts.push({ label: fam.name, action:"go-genera", current: nav.screen==="genera" });
    if(gen) parts.push({ label: gen.name, action:"go-species-list", current: nav.screen==="species" });
    if(sp) parts.push({ label: (gen.name + " " + sp.epithet), action:"", current: nav.screen==="detail" });

    bc.innerHTML = parts.map(function(p, i){
      var sep = i>0 ? '<span class="sep">›</span>' : "";
      if(p.current || !p.action){
        return sep + '<button class="current" disabled>' + esc(p.label) + '</button>';
      }
      return sep + '<button data-action="' + p.action + '">' + esc(p.label) + '</button>';
    }).join("");
  }

  function renderFamilies(){
    if(db.families.length===0){
      return '<div class="empty">' + emptySprigSVG() +
        '<p>Votre herbier est vide.<br>Commencez par ajouter la première famille botanique que vous avez croisée.</p>' +
        '<button class="btn btn-primary" data-action="open-add-family">Ajouter une famille</button></div>';
    }
    var fams = sortByName(db.families);
    var totalGenera = db.families.reduce(function(sum, f){ return sum + f.genera.length; }, 0);
    var totalSpecies = db.families.reduce(function(sum, f){
      return sum + f.genera.reduce(function(s, g){ return s + g.species.length; }, 0);
    }, 0);
    var rows = fams.map(function(f){
      return '<li class="row" data-action="open-family" data-id="' + f.id + '">' +
        '<div class="row-main"><div class="row-title">' + esc(f.name) + '</div>' +
        '<div class="row-count">' + plural(f.genera.length,"genre","genres") + '</div></div>' +
        '<button class="trash" data-action="delete-family" data-id="' + f.id + '" title="Supprimer">🗑</button>' +
        '<span class="chevron">›</span></li>';
    }).join("");
    return '<div class="eyebrow">Index par famille</div>' +
      '<p class="sub">' + plural(db.families.length,"famille","familles") +
        ' · ' + plural(totalGenera,"genre","genres") +
        ' · ' + plural(totalSpecies,"espèce","espèces") + '</p>' +
      '<ul class="list">' + rows + '</ul>';
  }

  function renderGenera(){
    var fam = getFamily(nav.familyId);
    if(!fam){ nav.screen="families"; return renderFamilies(); }
    if(fam.genera.length===0){
      return '<div class="eyebrow">' + esc(fam.name) + '</div><h1 class="title">Aucun genre</h1>' +
        '<div class="empty">' + emptySprigSVG() +
        '<p>Ajoutez un premier genre pour la famille des <strong>' + esc(fam.name) + '</strong>.</p>' +
        '<button class="btn btn-primary" data-action="open-add-genus">Ajouter un genre</button></div>';
    }
    var gens = sortByName(fam.genera);
    var rows = gens.map(function(g){
      return '<li class="row genus" data-action="open-genus" data-id="' + g.id + '">' +
        '<div class="row-main"><div class="row-title italic">' + esc(g.name) + '</div>' +
        '<div class="row-count">' + plural(g.species.length,"espèce","espèces") + '</div></div>' +
        '<button class="trash" data-action="delete-genus" data-id="' + g.id + '" title="Supprimer">🗑</button>' +
        '<span class="chevron">›</span></li>';
    }).join("");
    return '<div class="eyebrow">Famille</div>' +
      '<h1 class="title">' + esc(fam.name) + '</h1>' +
      '<p class="sub">' + plural(fam.genera.length,"genre","genres") + '</p>' +
      '<ul class="list">' + rows + '</ul>';
  }

  function renderSpeciesList(){
    var fam = getFamily(nav.familyId);
    var gen = getGenus(fam, nav.genusId);
    if(!gen){ nav.screen="genera"; return renderGenera(); }
    if(gen.species.length===0){
      return '<div class="eyebrow">' + esc(fam.name) + '</div><h1 class="title" style="font-style:italic">' + esc(gen.name) + '</h1>' +
        '<div class="empty">' + emptySprigSVG() +
        '<p>Aucune espèce de <em>' + esc(gen.name) + '</em> pour l\'instant.</p>' +
        '<button class="btn btn-primary" data-action="open-add-species">Ajouter une espèce</button></div>';
    }
    var sps = gen.species.slice().sort(function(a,b){ return a.epithet.localeCompare(b.epithet,"fr",{sensitivity:"base"}); });
    var rows = sps.map(function(s){
      return '<li class="row species" data-action="open-species" data-id="' + s.id + '">' +
        '<div class="row-main"><div class="row-title italic">' + esc(gen.name) + ' ' + esc(s.epithet) + '</div>' +
        '<div class="row-count">' + (s.commonName ? esc(s.commonName) : "Sans nom commun") + '</div></div>' +
        '<button class="trash" data-action="delete-species" data-id="' + s.id + '" title="Supprimer">🗑</button>' +
        '<span class="chevron">›</span></li>';
    }).join("");
    return '<div class="eyebrow">' + esc(fam.name) + '</div>' +
      '<h1 class="title" style="font-style:italic">' + esc(gen.name) + '</h1>' +
      '<p class="sub">' + plural(gen.species.length,"espèce","espèces") + '</p>' +
      '<ul class="list">' + rows + '</ul>';
  }

  function renderDetail(){
    var fam = getFamily(nav.familyId);
    var gen = getGenus(fam, nav.genusId);
    var sp = getSpecies(gen, nav.speciesId);
    if(!sp){ nav.screen="species"; return renderSpeciesList(); }

    var descHtml = sp.description
      ? '<div class="description-box">' + esc(sp.description) + '</div>'
      : '<div class="description-box placeholder">Aucune description pour l\'instant.</div>';

    var notesHtml = sp.notes.length
      ? sp.notes.map(function(n){
          return '<div class="note-card"><div class="note-date">' + esc(n.date) + '</div>' +
            '<div class="note-text">' + esc(n.text) + '</div>' +
            '<button class="note-del" data-action="delete-note" data-id="' + n.id + '" title="Supprimer">✕</button></div>';
        }).join("")
      : '<p class="sub" style="margin:0">Aucune note personnelle pour l\'instant.</p>';

    return '' +
      '<div class="detail-card">' +
        '<div class="sprig-divider">' + sprigSVG() + '</div>' +
        '<p class="binomial">' + esc(gen.name) + ' ' + esc(sp.epithet) + '</p>' +
        (sp.commonName ? '<p class="common-name">' + esc(sp.commonName) + '</p>' : '') +
        '<span class="family-tag">' + esc(fam.name) + '</span>' +
      '</div>' +

      '<div class="section-label">Description</div>' +
      descHtml +
      '<div style="margin-top:8px"><button class="link-btn" data-action="edit-description">' + (sp.description ? "Modifier la description" : "Ajouter une description") + '</button></div>' +

      '<div class="section-label">Notes personnelles</div>' +
      notesHtml +
      '<div style="margin-top:8px"><button class="link-btn" data-action="add-note">+ Ajouter une note</button></div>' +

      '<div class="danger-zone">' +
        '<button class="btn btn-danger" data-action="delete-species-detail">Supprimer cette espèce</button>' +
      '</div>';
  }

  function renderSearchResults(){
    var q = searchQuery.toLowerCase();
    var results = [];
    db.families.forEach(function(fam){
      fam.genera.forEach(function(gen){
        gen.species.forEach(function(sp){
          var hay = (fam.name + " " + gen.name + " " + sp.epithet + " " + sp.commonName).toLowerCase();
          if(hay.indexOf(q) !== -1){
            results.push({ fam:fam, gen:gen, sp:sp });
          }
        });
      });
    });
    if(results.length===0){
      return '<div class="empty">' + emptySprigSVG() + '<p>Aucune plante ne correspond à «\u00A0' + esc(searchQuery) + '\u00A0».</p></div>';
    }
    var rows = results.map(function(r){
      return '<li class="row species" data-action="open-species-search" data-fam="' + r.fam.id + '" data-gen="' + r.gen.id + '" data-sp="' + r.sp.id + '">' +
        '<div class="row-main"><div class="row-title italic">' + esc(r.gen.name) + ' ' + esc(r.sp.epithet) + '</div>' +
        '<div class="row-count">' + esc(r.fam.name) + (r.sp.commonName ? " · " + esc(r.sp.commonName) : "") + '</div></div>' +
        '<span class="chevron">›</span></li>';
    }).join("");
    return '<p class="sub">' + plural(results.length,"résultat","résultats") + '</p><ul class="list">' + rows + '</ul>';
  }

  /* ---------- MODAL ---------- */
  function openModal(html){
    document.getElementById("modalSheet").innerHTML = '<div class="modal-handle"></div>' + html;
    document.getElementById("modalOverlay").classList.remove("hidden");
    var firstInput = document.querySelector("#modalSheet input, #modalSheet textarea");
    if(firstInput) setTimeout(function(){ firstInput.focus(); }, 60);
  }
  function closeModal(){
    document.getElementById("modalOverlay").classList.add("hidden");
    document.getElementById("modalSheet").innerHTML = "";
  }

  function formAddFamily(){
    openModal(
      '<h2>Ajouter une famille</h2>' +
      '<form id="formAddFamily">' +
        '<div class="field"><label>Nom de la famille</label><input type="text" name="name" placeholder="ex. Rosaceae" required></div>' +
        '<div class="modal-actions"><button type="button" class="btn btn-ghost" data-action="close-modal">Annuler</button>' +
        '<button type="submit" class="btn btn-primary">Ajouter</button></div>' +
      '</form>'
    );
  }
  function formAddGenus(){
    var fam = getFamily(nav.familyId);
    openModal(
      '<h2>Ajouter un genre</h2><p class="sub" style="margin-top:-8px">Famille : ' + esc(fam.name) + '</p>' +
      '<form id="formAddGenus">' +
        '<div class="field"><label>Nom du genre</label><input type="text" name="name" placeholder="ex. Rosa" required></div>' +
        '<div class="modal-actions"><button type="button" class="btn btn-ghost" data-action="close-modal">Annuler</button>' +
        '<button type="submit" class="btn btn-primary">Ajouter</button></div>' +
      '</form>'
    );
  }
  function formAddSpecies(){
    var fam = getFamily(nav.familyId);
    var gen = getGenus(fam, nav.genusId);
    openModal(
      '<h2>Ajouter une espèce</h2><p class="sub" style="margin-top:-8px">Genre : <em>' + esc(gen.name) + '</em></p>' +
      '<form id="formAddSpecies">' +
        '<div class="field"><label>Épithète spécifique</label><input type="text" name="epithet" placeholder="ex. canina" required>' +
          '<div class="field-hint">Le nom complet sera « ' + esc(gen.name) + ' … »</div></div>' +
        '<div class="field"><label>Nom commun (optionnel)</label><input type="text" name="commonName" placeholder="ex. Églantier"></div>' +
        '<div class="field"><label>Description (optionnel)</label><textarea name="description" rows="3" placeholder="Caractéristiques, habitat…"></textarea></div>' +
        '<div class="modal-actions"><button type="button" class="btn btn-ghost" data-action="close-modal">Annuler</button>' +
        '<button type="submit" class="btn btn-primary">Ajouter</button></div>' +
      '</form>'
    );
  }
  function formEditDescription(){
    var fam = getFamily(nav.familyId), gen = getGenus(fam, nav.genusId), sp = getSpecies(gen, nav.speciesId);
    openModal(
      '<h2>Description — <em style="font-family:var(--serif)">' + esc(gen.name) + ' ' + esc(sp.epithet) + '</em></h2>' +
      '<form id="formEditDescription">' +
        '<div class="field"><textarea name="description" rows="6" placeholder="Décrivez cette espèce…">' + esc(sp.description) + '</textarea></div>' +
        '<div class="modal-actions"><button type="button" class="btn btn-ghost" data-action="close-modal">Annuler</button>' +
        '<button type="submit" class="btn btn-primary">Enregistrer</button></div>' +
      '</form>'
    );
  }
  function formAddNote(){
    var fam = getFamily(nav.familyId), gen = getGenus(fam, nav.genusId), sp = getSpecies(gen, nav.speciesId);
    openModal(
      '<h2>Note personnelle</h2><p class="sub" style="margin-top:-8px">' + esc(gen.name) + ' ' + esc(sp.epithet) + ' · ' + today() + '</p>' +
      '<form id="formAddNote">' +
        '<div class="field"><textarea name="text" rows="4" placeholder="Où l\'avez-vous croisée ? À quoi ressemblait-elle ?" required></textarea></div>' +
        '<div class="modal-actions"><button type="button" class="btn btn-ghost" data-action="close-modal">Annuler</button>' +
        '<button type="submit" class="btn btn-primary">Ajouter la note</button></div>' +
      '</form>'
    );
  }
  function formMenu(){
    openModal(
      '<h2>Menu</h2>' +
      '<div class="action-list">' +
        '<button data-action="refresh-data">Actualiser depuis le serveur</button>' +
        '<button data-action="export-data">Exporter mes données (fichier .json)</button>' +
        '<button data-action="trigger-import">Importer des données</button>' +
        '<a href="?logout=1">Se déconnecter</a>' +
        '<button class="danger" data-action="clear-all">Tout effacer</button>' +
      '</div>'
    );
  }

  /* ---------- EXPORT / IMPORT ---------- */
  function exportData(){
    var blob = new Blob([JSON.stringify(db, null, 2)], { type:"application/json" });
    var url = URL.createObjectURL(blob);
    var a = document.createElement("a");
    a.href = url;
    a.download = "herbier-" + new Date().toISOString().slice(0,10) + ".json";
    document.body.appendChild(a); a.click(); document.body.removeChild(a);
    URL.revokeObjectURL(url);
    toast("Export lancé ✓");
  }
  function importData(file){
    var reader = new FileReader();
    reader.onload = function(){
      try{
        var parsed = JSON.parse(reader.result);
        if(!parsed || !Array.isArray(parsed.families)) throw new Error("format invalide");
        db = parsed; saveDB();
        nav = { screen:"families", familyId:null, genusId:null, speciesId:null };
        render();
        toast("Import réussi ✓");
      }catch(e){ toast("⚠ Fichier illisible"); }
    };
    reader.readAsText(file);
  }

  /* ---------- EVENTS ---------- */
  document.addEventListener("click", function(e){
    var t = e.target.closest("[data-action]");
    if(!t) return;
    var action = t.dataset.action;

    switch(action){
      case "go-families":
        nav = { screen:"families", familyId:null, genusId:null, speciesId:null }; render(); break;
      case "go-genera":
        nav.screen="genera"; nav.genusId=null; nav.speciesId=null; render(); break;
      case "go-species-list":
        nav.screen="species"; nav.speciesId=null; render(); break;

      case "open-family":
        nav = { screen:"genera", familyId:t.dataset.id, genusId:null, speciesId:null }; render(); break;
      case "open-genus":
        nav.screen="species"; nav.genusId=t.dataset.id; nav.speciesId=null; render(); break;
      case "open-species":
        nav.screen="detail"; nav.speciesId=t.dataset.id; render(); break;
      case "open-species-search":
        nav = { screen:"detail", familyId:t.dataset.fam, genusId:t.dataset.gen, speciesId:t.dataset.sp };
        searchQuery = ""; document.getElementById("searchInput").value = ""; render(); break;

      case "delete-family":
        e.stopPropagation();
        if(confirm("Supprimer cette famille et tout son contenu (genres, espèces, notes) ?")){
          deleteFamily(t.dataset.id); render(); toast("Famille supprimée");
        } break;
      case "delete-genus":
        e.stopPropagation();
        if(confirm("Supprimer ce genre et toutes ses espèces ?")){
          deleteGenus(getFamily(nav.familyId), t.dataset.id); render(); toast("Genre supprimé");
        } break;
      case "delete-species":
        e.stopPropagation();
        if(confirm("Supprimer cette espèce et ses notes ?")){
          deleteSpecies(getGenus(getFamily(nav.familyId), nav.genusId), t.dataset.id); render(); toast("Espèce supprimée");
        } break;
      case "delete-species-detail":
        if(confirm("Supprimer cette espèce et ses notes ?")){
          deleteSpecies(getGenus(getFamily(nav.familyId), nav.genusId), nav.speciesId);
          nav.screen="species"; nav.speciesId=null; render(); toast("Espèce supprimée");
        } break;
      case "delete-note":
        if(confirm("Supprimer cette note ?")){
          var fam=getFamily(nav.familyId), gen=getGenus(fam,nav.genusId), sp=getSpecies(gen,nav.speciesId);
          deleteNote(sp, t.dataset.id); render(); toast("Note supprimée");
        } break;

      case "open-add-family": formAddFamily(); break;
      case "open-add-genus": formAddGenus(); break;
      case "open-add-species": formAddSpecies(); break;
      case "edit-description": formEditDescription(); break;
      case "add-note": formAddNote(); break;
      case "open-menu": formMenu(); break;
      case "close-modal": closeModal(); break;

      case "export-data": closeModal(); exportData(); break;
      case "trigger-import": closeModal(); document.getElementById("importFile").click(); break;
      case "refresh-data":
        closeModal();
        fetchDB().then(function(){ render(); toast("Données actualisées ✓"); })
          .catch(function(){ toast("⚠ Échec de l'actualisation"); });
        break;
      case "clear-all":
        if(confirm("Effacer TOUT le répertoire ? Cette action est irréversible.")){
          db = { families: [] }; saveDB();
          nav = { screen:"families", familyId:null, genusId:null, speciesId:null };
          closeModal(); render(); toast("Répertoire effacé");
        } break;
    }
  });

  document.getElementById("fab").addEventListener("click", function(){
    if(searchQuery) return;
    if(nav.screen==="families") formAddFamily();
    else if(nav.screen==="genera") formAddGenus();
    else if(nav.screen==="species") formAddSpecies();
    else if(nav.screen==="detail"){
      openModal(
        '<h2>Ajouter…</h2>' +
        '<div class="action-list">' +
          '<button data-action="edit-description">Description de l\'espèce</button>' +
          '<button data-action="add-note">Note personnelle</button>' +
        '</div>'
      );
    }
  });

  document.getElementById("modalOverlay").addEventListener("click", function(e){
    if(e.target.id === "modalOverlay") closeModal();
  });

  document.addEventListener("submit", function(e){
    var id = e.target.id;
    if(id==="formAddFamily"){
      e.preventDefault();
      var name = e.target.name.value.trim();
      if(!name) return;
      addFamily(name); closeModal(); render(); toast("Famille ajoutée ✓");
    }
    else if(id==="formAddGenus"){
      e.preventDefault();
      var name = e.target.name.value.trim();
      if(!name) return;
      addGenus(nav.familyId, name); closeModal(); render(); toast("Genre ajouté ✓");
    }
    else if(id==="formAddSpecies"){
      e.preventDefault();
      var ep = e.target.epithet.value.trim();
      if(!ep) return;
      addSpecies(nav.familyId, nav.genusId, ep, e.target.commonName.value, e.target.description.value);
      closeModal(); render(); toast("Espèce ajoutée ✓");
    }
    else if(id==="formEditDescription"){
      e.preventDefault();
      updateDescription(nav.familyId, nav.genusId, nav.speciesId, e.target.description.value);
      closeModal(); render(); toast("Description enregistrée ✓");
    }
    else if(id==="formAddNote"){
      e.preventDefault();
      var text = e.target.text.value.trim();
      if(!text) return;
      addNote(nav.familyId, nav.genusId, nav.speciesId, text);
      closeModal(); render(); toast("Note ajoutée ✓");
    }
  });

  document.getElementById("searchInput").addEventListener("input", function(e){
    searchQuery = e.target.value.trim();
    render();
  });

  document.getElementById("importFile").addEventListener("change", function(e){
    var file = e.target.files[0];
    if(file) importData(file);
    e.target.value = "";
  });

  document.addEventListener("click", function(e){
    if(e.target.closest('[data-action="retry-load"]')) init();
  });

  /* ---------- INIT ---------- */
  function init(){
    document.getElementById("content").innerHTML = '<div class="empty"><p>Chargement de l\'herbier…</p></div>';
    fetchDB().then(function(){ render(); }).catch(function(){
      document.getElementById("content").innerHTML =
        '<div class="empty"><p>Impossible de charger vos données.<br>Vérifiez votre connexion puis réessayez.</p>' +
        '<button class="btn btn-primary" data-action="retry-load">Réessayer</button></div>';
    });
  }
  init();
})();
</script>
</body>
</html>
