// Vocal API Docs — single-page React app.
// Sidebar (sections) | Contenu Markdown-like | Right rail (Auth + clé API).
// "Try it" intégré : utilise la clé API saisie (ou récupérée depuis my.helvia.app
// via localStorage si le user vient de là) pour exécuter les requêtes en live.

const { useState, useEffect, useMemo, useCallback } = React;

const API_BASE = 'https://api.helvia.app';
const MY_BASE  = 'https://my.helvia.app';

const LS = {
  apiKey:  'vocal_docs_apikey',
  myToken: 'vocal_my_token', // partagé avec my.helvia.app (même origin .helvia.app)
};

// ============================================================================
// Auth context : on garde la clé API saisie (apiKey) ET le bearer my.helvia.app
// (myToken) pour récupérer automatiquement les clés API du user connecté.
// ============================================================================
// Récupère un éventuel ?apikey=... ou #apikey=... passé depuis my.helvia.app
// pour faciliter le pré-remplissage à la première visite.
function readPrefilledKey() {
  try {
    const search = new URLSearchParams(location.search);
    if (search.get('apikey')) return search.get('apikey');
    const h = (location.hash || '').replace(/^#/, '');
    const params = new URLSearchParams(h.includes('=') ? h : '');
    if (params.get('apikey')) return params.get('apikey');
  } catch (e) {}
  return null;
}

const useAuth = () => {
  const prefilled = readPrefilledKey();
  const [apiKey, setApiKey] = useState(() => prefilled || localStorage.getItem(LS.apiKey) || '');
  const [myToken, setMyToken] = useState(() => localStorage.getItem(LS.myToken) || '');
  // Nettoie l'URL après extraction (on garde la clé seulement en localStorage).
  useEffect(() => {
    if (prefilled) {
      const url = new URL(location.href); url.searchParams.delete('apikey');
      history.replaceState(null, '', url.pathname + url.search);
    }
  }, []);
  const [me, setMe] = useState(null);
  const [keys, setKeys] = useState([]);

  useEffect(() => { localStorage.setItem(LS.apiKey, apiKey || ''); }, [apiKey]);

  // Charge le profil + les clés API du user via my.helvia.app si le token y est.
  const refresh = useCallback(async () => {
    if (!myToken) { setMe(null); setKeys([]); return; }
    try {
      const meRes = await fetch(`${API_BASE}/me`, { headers: { Authorization: `Bearer ${myToken}` } });
      if (!meRes.ok) { setMe(null); setKeys([]); return; }
      const meData = await meRes.json();
      setMe(meData?.user || null);
      const kRes = await fetch(`${API_BASE}/my/api-keys`, { headers: { Authorization: `Bearer ${myToken}` } });
      if (kRes.ok) {
        const kData = await kRes.json();
        setKeys(kData?.api_keys || kData?.keys || []);
      }
    } catch (e) { /* silent */ }
  }, [myToken]);

  useEffect(() => { refresh(); }, [refresh]);

  return { apiKey, setApiKey, myToken, setMyToken, me, keys, refresh };
};

// ============================================================================
// Sidebar : navigation par section.
// ============================================================================
const SECTIONS = [
  { group: 'Démarrage', items: [
    { id: 'intro',   label: 'Introduction' },
    { id: 'auth',    label: 'Authentification' },
    { id: 'errors',  label: 'Codes d\'erreur' },
    { id: 'recreate', label: 'Recréer l\'inbox dans son app' },
  ]},
  { group: 'WhatsApp — Envoi', items: [
    { id: 'wa-template',  label: 'Envoyer un template' },
    { id: 'wa-freeform',  label: 'Envoyer un message libre (24h)' },
  ]},
  { group: 'WhatsApp — Inbox', items: [
    { id: 'wa-conv-list',     label: 'Lister les conversations' },
    { id: 'wa-conv-messages', label: 'Lire les messages' },
    { id: 'wa-conv-reply',    label: 'Répondre dans une conversation' },
    { id: 'wa-conv-bot',      label: 'Activer / désactiver le bot' },
  ]},
  { group: 'WhatsApp — Webhooks', items: [
    { id: 'wa-wh-buttons', label: 'Click sur un bouton de template' },
  ]},
  { group: 'Référence', items: [
    { id: 'sdk',       label: 'Exemples (curl / Node / Python / PHP)' },
    { id: 'changelog', label: 'Changelog' },
  ]},
];

const Sidebar = ({ active, onNavigate }) => (
  <aside className="sidebar">
    <div className="sidebar-brand">
      <img src="https://my.helvia.app/assets/img/vocal-logotype.svg" alt="Vocal" />
      <span className="tag">DOCS</span>
    </div>
    {SECTIONS.map(s => (
      <div key={s.group}>
        <div className="sidebar-section-title">{s.group}</div>
        {s.items.map(it => (
          <a key={it.id}
             className={`sidebar-link ${active === it.id ? 'active' : ''}`}
             onClick={() => onNavigate(it.id)}>
            {it.label}
          </a>
        ))}
      </div>
    ))}
    <div className="sidebar-section-title">Liens</div>
    <a className="sidebar-link" href={MY_BASE} target="_blank" rel="noopener">↗ Espace client</a>
    <a className="sidebar-link" href="mailto:support@helvia.app">↗ Support</a>
  </aside>
);

// ============================================================================
// Right rail : auth + clé API rapide.
// ============================================================================
const RightRail = ({ auth }) => {
  const { apiKey, setApiKey, me, keys, myToken } = auth;
  const isConnected = !!me;
  return (
    <aside className="right-rail">
      <h4>Authentification</h4>
      {isConnected ? (
        <div className="banner-auth banner-ok">
          <div>
            <strong>Connecté</strong><br/>
            <span className="muted">{me.email}</span>
          </div>
          <a className="btn btn-sm" href={MY_BASE} target="_blank" rel="noopener">Espace ↗</a>
        </div>
      ) : (
        <div className="banner-auth">
          <div>
            <strong>Non connecté</strong><br/>
            <span className="muted">Connectez-vous pour récupérer vos clés API automatiquement.</span>
          </div>
          <a className="btn btn-sm btn-primary" href={MY_BASE}>Se connecter</a>
        </div>
      )}

      <h4 style={{ marginTop: '1.25rem' }}>Clé API</h4>
      <input
        type="text" value={apiKey} placeholder="vk_..." autoComplete="off"
        onChange={e => setApiKey(e.target.value.trim())} />
      {isConnected && keys.length > 0 && (
        <>
          <div className="muted" style={{ marginTop: '0.5rem' }}>Choisir parmi vos clés :</div>
          {keys.filter(k => k.is_active).map(k => (
            <button key={k.id} className="btn btn-sm" style={{ marginTop: '0.3rem', width: '100%', textAlign: 'left' }}
              onClick={() => setApiKey(k.key_value || k.key || '')}>
              {k.label || `Clé #${k.id}`}
              <div className="muted" style={{ fontSize: '0.7rem' }}>
                {(k.key_value || k.key || '').slice(0, 14)}…
              </div>
            </button>
          ))}
        </>
      )}
      {isConnected && keys.length === 0 && (
        <div className="muted" style={{ marginTop: '0.5rem' }}>
          Aucune clé pour l'instant.{' '}
          <a href={`${MY_BASE}/#api-keys`} target="_blank" rel="noopener">Créer une clé →</a>
        </div>
      )}
      <div className="muted" style={{ marginTop: '0.75rem', fontSize: '0.78rem' }}>
        Cette clé est utilisée localement pour les boutons « Tester en live » de cette doc.
      </div>
    </aside>
  );
};

// ============================================================================
// Composant Try-it : panneau live de test d'un endpoint.
// ============================================================================
const TryIt = ({ apiKey, defaults, schema, path: epPath = '/v1/messages/whatsapp/template', method = 'POST', pathParams = [], queryParams = [] }) => {
  // schema   : [{ key, label, placeholder, type?, options?, required? }] pour le BODY (POST/PUT)
  // queryParams : [{ key, label, placeholder }] pour le query string
  // pathParams  : [{ key, label, placeholder }] injectés dans le path en remplaçant `:key`
  const [form, setForm] = useState(defaults || {});
  const [resp, setResp] = useState(null);
  const [busy, setBusy] = useState(false);
  const [reqPreview, setReqPreview] = useState(false);

  const hasBody = method !== 'GET' && method !== 'DELETE';

  const buildUrl = () => {
    let p = epPath;
    for (const f of pathParams) {
      const v = form[f.key];
      p = p.replace(':' + f.key, encodeURIComponent(v || ''));
    }
    const qs = new URLSearchParams();
    for (const f of queryParams) {
      const v = form[f.key];
      if (v !== undefined && v !== '') qs.set(f.key, v);
    }
    const q = qs.toString();
    return `${API_BASE}${p}${q ? '?' + q : ''}`;
  };

  const buildBody = () => {
    if (!hasBody) return null;
    const body = {};
    for (const f of schema) {
      const v = form[f.key];
      if (v === undefined || v === '') continue;
      if (f.type === 'json') {
        try { body[f.key] = JSON.parse(v); }
        catch (e) { throw new Error(`${f.label} : JSON invalide (${e.message})`); }
      } else if (f.type === 'number') {
        body[f.key] = Number(v);
      } else { body[f.key] = v; }
    }
    return body;
  };

  const run = async () => {
    setBusy(true); setResp(null);
    try {
      const url = buildUrl();
      let body = null;
      try { body = buildBody(); }
      catch (e) { setResp({ status: 0, body: { error: e.message } }); setBusy(false); return; }
      const init = {
        method,
        headers: { 'Authorization': `Bearer ${apiKey || ''}` },
      };
      if (hasBody) {
        init.headers['Content-Type'] = 'application/json';
        init.body = JSON.stringify(body || {});
      }
      const r = await fetch(url, init);
      const text = await r.text();
      let parsed; try { parsed = JSON.parse(text); } catch { parsed = { raw: text }; }
      setResp({ status: r.status, body: parsed });
    } catch (e) {
      setResp({ status: 0, body: { error: e.message || String(e) } });
    } finally { setBusy(false); }
  };

  const curlSnippet = useMemo(() => {
    const url = buildUrl();
    const lines = [`curl -X ${method} '${url}' \\`,
      `  -H 'Authorization: Bearer ${apiKey || '<YOUR_API_KEY>'}'`];
    if (hasBody) {
      let body = {};
      try { body = buildBody() || {}; } catch (e) { body = { error: e.message }; }
      lines[lines.length - 1] += ' \\';
      lines.push(`  -H 'Content-Type: application/json' \\`);
      lines.push(`  -d '${JSON.stringify(body, null, 2)}'`);
    }
    return lines.join('\n');
  }, [form, apiKey, schema, epPath, method, pathParams, queryParams]);

  return (
    <div className="tryit">
      <div className="tryit-title">
        <span>🧪 Tester en live</span>
        <span className="tag">{method} {epPath}</span>
      </div>
      {!apiKey && (
        <div className="callout callout-warning" style={{ marginBottom: '0.75rem' }}>
          Renseignez votre <strong>clé API</strong> à droite pour activer le test.
        </div>
      )}

      {pathParams.map(f => (
        <div key={'p_' + f.key}>
          <label>{f.label} <span className="muted" style={{ fontSize: '0.7rem' }}>(path)</span></label>
          <input type="text" value={form[f.key] || ''} placeholder={f.placeholder}
            onChange={e => setForm({ ...form, [f.key]: e.target.value })} />
        </div>
      ))}

      {queryParams.map(f => (
        <div key={'q_' + f.key}>
          <label>{f.label} <span className="muted" style={{ fontSize: '0.7rem' }}>(query)</span></label>
          <input type="text" value={form[f.key] || ''} placeholder={f.placeholder}
            onChange={e => setForm({ ...form, [f.key]: e.target.value })} />
        </div>
      ))}

      {hasBody && schema.map(f => (
        <div key={f.key}>
          <label>{f.label}{f.required && <span style={{ color: 'var(--danger)' }}> *</span>}</label>
          {f.type === 'textarea' || f.type === 'json' ? (
            <textarea rows={f.rows || 4} value={form[f.key] || ''}
              placeholder={f.placeholder}
              onChange={e => setForm({ ...form, [f.key]: e.target.value })} />
          ) : f.type === 'select' ? (
            <select value={form[f.key] || ''} onChange={e => setForm({ ...form, [f.key]: e.target.value })}>
              {(f.options || []).map(o => <option key={o.value} value={o.value}>{o.label}</option>)}
            </select>
          ) : (
            <input type="text" value={form[f.key] || ''}
              placeholder={f.placeholder}
              onChange={e => setForm({ ...form, [f.key]: e.target.value })} />
          )}
        </div>
      ))}
      <div className="tryit-actions">
        <button className="btn btn-primary" disabled={busy || !apiKey} onClick={run}>
          {busy ? 'Envoi…' : 'Envoyer'}
        </button>
        <button className="btn btn-sm" onClick={() => setReqPreview(p => !p)}>{reqPreview ? 'Masquer' : 'Voir'} la requête</button>
      </div>
      {reqPreview && <div className="response-box"><pre><code>{curlSnippet}</code></pre></div>}
      {resp && (
        <div className="response-box">
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
            <strong>Réponse</strong>
            <span className={`tag`} style={{ background: resp.status >= 200 && resp.status < 300 ? '#dcfce7' : '#fee2e2', color: resp.status >= 200 && resp.status < 300 ? '#166534' : '#991b1b' }}>
              HTTP {resp.status}
            </span>
          </div>
          <pre><code>{JSON.stringify(resp.body, null, 2)}</code></pre>
        </div>
      )}
    </div>
  );
};

// ============================================================================
// Contenu des sections.
// ============================================================================
const SectionIntro = () => (
  <>
    <h1>API Vocal</h1>
    <p className="muted">REST API JSON, endpoints stables, authentifiés par clé API. Idéal pour automatiser l'envoi de notifications WhatsApp transactionnelles depuis votre back-office.</p>
    <h2>Base URL</h2>
    <pre><code>{API_BASE}</code></pre>
    <h2>Format</h2>
    <p>Toutes les requêtes et réponses sont en JSON. Les requêtes qui modifient des données prennent un body <code>Content-Type: application/json</code>.</p>
    <h2>Conventions</h2>
    <ul>
      <li>Tous les numéros de téléphone sont au format <strong>E.164</strong> (ex: <code>+41225394800</code>).</li>
      <li>Les horodatages sont en <strong>UTC ISO 8601</strong>.</li>
      <li>Les montants sont en <strong>CHF</strong> sauf indication contraire.</li>
    </ul>
  </>
);

const SectionAuth = () => (
  <>
    <h1>Authentification</h1>
    <p>Toutes les requêtes API doivent inclure un header <code>Authorization</code> avec votre clé API en Bearer token :</p>
    <pre><code>{`Authorization: Bearer vk_XXXXXXXXXXXXXXXXXXXXXXXX`}</code></pre>
    <h2>Où trouver ma clé ?</h2>
    <p>Dans <a href={`${MY_BASE}/#api-keys`} target="_blank" rel="noopener">l'espace client → API</a>, créez une clé et choisissez les lignes auxquelles elle a accès. Une clé peut être révoquée ou limitée à tout moment.</p>
    <div className="callout callout-warning">
      <strong>Ne partagez jamais votre clé.</strong> Elle a accès à vos lignes et peut envoyer des messages facturés.
    </div>
  </>
);

const SectionErrors = () => (
  <>
    <h1>Codes d'erreur</h1>
    <p>Les erreurs renvoient un statut HTTP standard et un body JSON <code>{`{ "error": "...", "code": ... }`}</code>.</p>
    <table>
      <thead><tr><th>HTTP</th><th>Signification</th><th>Action</th></tr></thead>
      <tbody>
        <tr><td><code>400</code></td><td>Paramètres invalides</td><td>Vérifier le body de la requête (format E.164, JSON valide…).</td></tr>
        <tr><td><code>401</code></td><td>Clé API manquante / invalide</td><td>Header <code>Authorization: Bearer …</code>.</td></tr>
        <tr><td><code>403</code></td><td>Ligne non autorisée par cette clé</td><td>Ajouter la ligne au scope de la clé dans my.helvia.app.</td></tr>
        <tr><td><code>404</code></td><td>Ligne ou template introuvable</td><td>Vérifier l'orthographe du <code>template</code> et de <code>from</code>.</td></tr>
        <tr><td><code>409</code></td><td>Template non approuvé</td><td>Soumettre le template à WhatsApp et attendre l'approbation.</td></tr>
        <tr><td><code>502</code></td><td>Erreur opérateur (Twilio / Meta)</td><td>Détail dans le champ <code>detail</code>. Réessayer plus tard.</td></tr>
      </tbody>
    </table>
  </>
);

const SectionWATemplate = ({ apiKey, defaultFrom, defaultTemplate }) => (
  <>
    <h1>Envoyer un template WhatsApp</h1>
    <p>Envoie un message en utilisant un <strong>template approuvé</strong> par WhatsApp (Content Template). Permet d'écrire à un client hors fenêtre 24h (rappel, confirmation, notification).</p>
    <div className="endpoint"><span className="badge badge-post">POST</span><code>{API_BASE}/v1/messages/whatsapp/template</code></div>

    <h2>Body</h2>
    <table>
      <thead><tr><th>Paramètre</th><th>Type</th><th>Description</th></tr></thead>
      <tbody>
        <tr><td><code>from</code> *</td><td>string</td><td>Numéro Vocal expéditeur (E.164). Doit être une ligne WhatsApp active autorisée par votre clé.</td></tr>
        <tr><td><code>to</code> *</td><td>string</td><td>Numéro destinataire (E.164).</td></tr>
        <tr><td><code>template</code> *</td><td>string</td><td>Nom interne du template (<code>friendly_name</code>) ou <code>content_sid</code> (HX…).</td></tr>
        <tr><td><code>variables</code></td><td>object</td><td>Map <code>{`{ "1": "valeur1", "2": "valeur2" }`}</code> pour remplir les placeholders <code>{`{{1}}`}, {`{{2}}`}</code> du template.</td></tr>
      </tbody>
    </table>

    <h2>Exemple</h2>
    <pre><code>{`curl -X POST '${API_BASE}/v1/messages/whatsapp/template' \\
  -H 'Authorization: Bearer vk_XXXX' \\
  -H 'Content-Type: application/json' \\
  -d '{
    "from":     "${defaultFrom || '+41225394800'}",
    "to":       "+41XXXXXXXXX",
    "template": "${defaultTemplate || 'rey_confirmation_v1'}",
    "variables": {
      "1": "Yannick",
      "2": "Genève → CDG",
      "3": "27 mai 2026",
      "4": "Genève Aéroport T1"
    }
  }'`}</code></pre>

    <h3>Réponse 200</h3>
    <pre><code>{`{
  "ok": true,
  "message_sid": "SMxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "status": "queued",
  "content_sid": "HXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "template": "rey_confirmation_v1",
  "from": "${defaultFrom || '+41225394800'}",
  "to": "+41XXXXXXXXX"
}`}</code></pre>

    <TryIt
      apiKey={apiKey}
      defaults={{
        from: defaultFrom || '+41225394800',
        to: '',
        template: defaultTemplate || '',
        variables: '{\n  "1": "Yannick",\n  "2": "10:30"\n}',
      }}
      schema={[
        { key: 'from',     label: 'from (votre ligne WhatsApp)', placeholder: '+41225394800', required: true },
        { key: 'to',       label: 'to (destinataire)', placeholder: '+41XXXXXXXXX', required: true },
        { key: 'template', label: 'template (friendly_name ou content_sid)', placeholder: 'rey_confirmation_v1', required: true },
        { key: 'variables', label: 'variables (JSON)', type: 'json', rows: 6, placeholder: '{ "1": "Yannick" }' },
      ]}
    />

    <h2>Bonnes pratiques</h2>
    <ul>
      <li>Donnez des <strong>exemples réalistes</strong> dans les variables lors de la création du template (sinon Meta rejette).</li>
      <li>Vérifiez le <code>status</code> via le webhook delivery ou en interrogeant <code>/api/whatsapp</code>.</li>
      <li>Idempotence : si vous envoyez deux fois la même requête, deux messages partiront. Gérez l'idempotence côté client (clé déduplication interne).</li>
    </ul>
  </>
);

const SectionRecreate = () => (
  <>
    <h1>Recréer l'inbox WhatsApp dans votre app</h1>
    <p className="muted">L'inbox <code>my.helvia.app/#whatsapp-inbox</code> (liste de conversations à gauche, fil de messages à droite, bouton « Bot IA actif ») peut être reproduite dans n'importe quelle application en consommant uniquement l'API publique <code>/v1/...</code> avec une clé API.</p>

    <h2>Architecture cible</h2>
    <pre><code>{`┌─────────────────┐         GET /v1/whatsapp/conversations
│ Votre app       │ ──────────────────────────────────────►  Vocal API (Cloudflare Worker)
│ (SPA / mobile / │ ◄──────────────────────────────────────  D1 (whatsapp_conversations / messages)
│  back-office)   │         GET /v1/whatsapp/conversations/:id/messages              │
│                 │         POST /v1/whatsapp/conversations/:id/reply                │
│                 │         POST /v1/whatsapp/conversations/:id/bot                  │
└─────────────────┘                                                                  │
                                                                                     ▼
                                                    Twilio (numéro WhatsApp Business) ⇄ utilisateurs
`}</code></pre>

    <h2>Étapes minimales</h2>
    <ol>
      <li><strong>Créer une clé API</strong> sur <a href={`${MY_BASE}/#api-keys`} target="_blank" rel="noopener">my.helvia.app → API</a> et la <strong>scoper aux lignes</strong> que votre app peut piloter.</li>
      <li><strong>Activer WhatsApp</strong> sur la / les ligne(s) (bouton « 💬 Activer WhatsApp » dans l'espace client). Cela importe le sender chez l'opérateur, configure les webhooks et active le bot IA si voulu.</li>
      <li><strong>Polling ou webhook ?</strong>
        <ul>
          <li><strong>Polling</strong> simple : votre app appelle <code>GET /v1/whatsapp/conversations</code> toutes les 5–10 s pour rafraîchir la liste, et <code>/messages</code> quand une conversation est ouverte.</li>
          <li><strong>Webhook entrant</strong> (à venir, en cours de spec) : Vocal pousse chaque message reçu sur un endpoint que vous fournissez. En attendant, le polling est largement suffisant (D1 est rapide, conversations indexées).</li>
        </ul>
      </li>
      <li><strong>Envoyer</strong> :
        <ul>
          <li>Si la fenêtre 24h est ouverte (l'utilisateur a écrit en dernier) → <code>POST /v1/whatsapp/conversations/:id/reply</code> ou <code>POST /v1/messages/whatsapp</code>.</li>
          <li>Sinon (rappel, notification proactive) → <code>POST /v1/messages/whatsapp/template</code> avec un template approuvé.</li>
        </ul>
      </li>
      <li><strong>Bot IA</strong> : laissez <code>bot_enabled = 1</code> par défaut sur chaque nouvelle conversation. Quand un humain prend la main dans votre UI, appelez <code>POST /v1/whatsapp/conversations/:id/bot</code> avec <code>{`{ enabled: 0 }`}</code> (ou utilisez <code>take_over: 1</code> dans <code>/reply</code>).</li>
    </ol>

    <h2>Modèle de données minimal côté client</h2>
    <p>Vous n'avez pas besoin de répliquer la DB Vocal. Il suffit de cacher localement ce que <code>GET /v1/whatsapp/conversations</code> et <code>/messages</code> renvoient pour afficher l'UI fluidement.</p>
    <pre><code>{`Conversation {
  id, line_id, line_phone, line_label,
  contact_number, contact_name,
  last_message_at, last_message_body, last_direction,
  unread_count, bot_enabled
}

Message {
  id, direction (inbound|outbound),
  contact_number, body, media_urls, num_media,
  message_sid, status, sent_by (user|bot|api|system),
  created_at
}`}</code></pre>

    <div className="callout callout-info">
      <strong>Sécurité.</strong> Ne jamais embarquer la clé API dans une SPA exposée publiquement.
      Faites passer toutes les requêtes par <strong>votre back-end</strong> qui ajoute le header <code>Authorization: Bearer ...</code>.
    </div>
  </>
);

const SectionWAFreeForm = ({ apiKey, defaultFrom }) => (
  <>
    <h1>Envoyer un message libre (fenêtre 24h)</h1>
    <p>Envoie un message texte <strong>free-form</strong> (sans template). N'est autorisé par WhatsApp que si l'utilisateur a écrit dans les <strong>24 dernières heures</strong>. Hors de cette fenêtre, utilisez <a href="#wa-template" onClick={() => location.hash = '#wa-template'}>un template approuvé</a>.</p>
    <div className="endpoint"><span className="badge badge-post">POST</span><code>{API_BASE}/v1/messages/whatsapp</code></div>

    <h2>Body</h2>
    <table>
      <thead><tr><th>Paramètre</th><th>Type</th><th>Description</th></tr></thead>
      <tbody>
        <tr><td><code>from</code> *</td><td>string</td><td>Numéro Vocal expéditeur (E.164).</td></tr>
        <tr><td><code>to</code> *</td><td>string</td><td>Numéro destinataire (E.164).</td></tr>
        <tr><td><code>body</code> *</td><td>string</td><td>Texte du message (1600 caractères max).</td></tr>
      </tbody>
    </table>

    <h3>Réponse 200</h3>
    <pre><code>{`{
  "ok": true,
  "message_sid": "SMxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "status": "queued",
  "from": "${defaultFrom || '+41225394800'}",
  "to": "+41XXXXXXXXX"
}`}</code></pre>

    <TryIt
      apiKey={apiKey}
      path="/v1/messages/whatsapp"
      method="POST"
      defaults={{ from: defaultFrom || '+41225394800', to: '', body: '' }}
      schema={[
        { key: 'from', label: 'from (votre ligne)', placeholder: '+41225394800', required: true },
        { key: 'to',   label: 'to (destinataire)', placeholder: '+41XXXXXXXXX', required: true },
        { key: 'body', label: 'body (texte)', type: 'textarea', rows: 4, placeholder: 'Bonjour…', required: true },
      ]}
    />

    <div className="callout callout-warning">
      Une réponse <code>HTTP 502</code> avec <code>code 63016</code> côté Twilio indique généralement une fenêtre 24h fermée → utilisez un template.
    </div>
  </>
);

const SectionWAConvList = ({ apiKey }) => (
  <>
    <h1>Lister les conversations WhatsApp</h1>
    <p>Récupère la liste paginée des conversations du client (toutes lignes confondues, ou filtré par <code>line_id</code>). Idéal pour la <strong>colonne de gauche</strong> de l'inbox.</p>
    <div className="endpoint"><span className="badge badge-get">GET</span><code>{API_BASE}/v1/whatsapp/conversations</code></div>

    <h2>Query string</h2>
    <table>
      <thead><tr><th>Param</th><th>Type</th><th>Description</th></tr></thead>
      <tbody>
        <tr><td><code>line_id</code></td><td>integer</td><td>Filtre sur une ligne. Sinon retourne toutes les lignes accessibles à la clé.</td></tr>
        <tr><td><code>page</code></td><td>integer</td><td>Page (défaut <code>1</code>).</td></tr>
        <tr><td><code>limit</code></td><td>integer</td><td>Taille de page (max <code>200</code>, défaut <code>50</code>).</td></tr>
      </tbody>
    </table>

    <h3>Réponse 200</h3>
    <pre><code>{`{
  "conversations": [
    {
      "id": 142,
      "line_id": 22,
      "line_phone": "+41225391450",
      "line_label": "ReyTransferts",
      "contact_number": "+41783020487",
      "contact_name": "Yannick",
      "last_message_at": "2026-05-29T15:49:12Z",
      "last_message_body": "Salut",
      "last_direction": "outbound",
      "unread_count": 0,
      "bot_enabled": 1,
      "created_at": "...", "updated_at": "..."
    }
  ],
  "total": 87, "page": 1, "total_pages": 2
}`}</code></pre>

    <TryIt
      apiKey={apiKey}
      path="/v1/whatsapp/conversations"
      method="GET"
      queryParams={[
        { key: 'line_id', label: 'line_id (optionnel)', placeholder: '22' },
        { key: 'page',    label: 'page', placeholder: '1' },
        { key: 'limit',   label: 'limit', placeholder: '50' },
      ]}
      schema={[]}
    />
  </>
);

const SectionWAConvMessages = ({ apiKey }) => (
  <>
    <h1>Lire les messages d'une conversation</h1>
    <p>Récupère le fil de messages d'une conversation (par défaut les <code>100</code> derniers, jusqu'à <code>500</code>), <strong>ordonnés du plus ancien au plus récent</strong>. Marque automatiquement la conversation comme lue (<code>unread_count = 0</code>).</p>
    <div className="endpoint"><span className="badge badge-get">GET</span><code>{API_BASE}/v1/whatsapp/conversations/:id/messages</code></div>

    <h3>Réponse 200</h3>
    <pre><code>{`{
  "conversation": {
    "id": 142, "line_id": 22,
    "contact_number": "+41783020487", "contact_name": "Yannick",
    "bot_enabled": 1, "last_message_at": "..."
  },
  "messages": [
    {
      "id": 901, "direction": "inbound",
      "contact_number": "+41783020487",
      "body": "Hello",
      "media_urls": null, "num_media": 0,
      "message_sid": "SM...", "status": "received",
      "sent_by": null,
      "created_at": "2026-05-28T13:48:00Z",
      "updated_at": "2026-05-28T13:48:00Z"
    },
    {
      "id": 902, "direction": "outbound",
      "body": "Salut ! Comment ça va ? 😊",
      "sent_by": "bot",
      "status": "delivered",
      ...
    }
  ]
}`}</code></pre>

    <TryIt
      apiKey={apiKey}
      path="/v1/whatsapp/conversations/:id/messages"
      method="GET"
      pathParams={[{ key: 'id', label: 'id (conversation)', placeholder: '142' }]}
      queryParams={[{ key: 'limit', label: 'limit', placeholder: '100' }]}
      schema={[]}
    />

    <h2>Champs <code>sent_by</code></h2>
    <ul>
      <li><code>user</code> : envoyé par un humain depuis l'inbox <code>my.helvia.app</code>.</li>
      <li><code>bot</code> : généré par le bot IA Vocal.</li>
      <li><code>api</code> : envoyé via l'API publique (votre app).</li>
      <li><code>system</code> : message technique (template auto, notification).</li>
      <li><code>null</code> : message <code>inbound</code> (envoyé par l'utilisateur final).</li>
    </ul>
  </>
);

const SectionWAConvReply = ({ apiKey }) => (
  <>
    <h1>Répondre dans une conversation</h1>
    <p>Envoie un message <strong>free-form</strong> dans une conversation existante (la fenêtre 24h doit être ouverte). Trace automatiquement le message en DB côté Vocal.</p>
    <div className="endpoint"><span className="badge badge-post">POST</span><code>{API_BASE}/v1/whatsapp/conversations/:id/reply</code></div>

    <h2>Body</h2>
    <table>
      <thead><tr><th>Param</th><th>Type</th><th>Description</th></tr></thead>
      <tbody>
        <tr><td><code>body</code> *</td><td>string</td><td>Texte du message (max 1600 car.).</td></tr>
        <tr><td><code>take_over</code></td><td>0 | 1</td><td>Si <code>1</code>, désactive le bot IA pour cette conversation après l'envoi (un humain a pris la main).</td></tr>
      </tbody>
    </table>

    <h3>Réponse 200</h3>
    <pre><code>{`{
  "ok": true,
  "message_sid": "SM...",
  "status": "queued",
  "bot_disabled": true
}`}</code></pre>

    <TryIt
      apiKey={apiKey}
      path="/v1/whatsapp/conversations/:id/reply"
      method="POST"
      pathParams={[{ key: 'id', label: 'id (conversation)', placeholder: '142' }]}
      defaults={{ take_over: '1' }}
      schema={[
        { key: 'body', label: 'body', type: 'textarea', rows: 3, placeholder: 'Bonjour, je prends le relais.', required: true },
        { key: 'take_over', label: 'take_over (0 ou 1)', placeholder: '1' },
      ]}
    />
  </>
);

const SectionWAConvBot = ({ apiKey }) => (
  <>
    <h1>Activer / désactiver le bot IA d'une conversation</h1>
    <p>Toggle indépendant par conversation. Pour désactiver globalement le bot d'une <strong>ligne</strong>, modifiez <code>wa_bot_enabled</code> sur la ligne (UI <code>my.helvia.app</code>).</p>
    <div className="endpoint"><span className="badge badge-post">POST</span><code>{API_BASE}/v1/whatsapp/conversations/:id/bot</code></div>

    <h2>Body</h2>
    <pre><code>{`{ "enabled": 0 }   // 0 = bot off, 1 = bot on`}</code></pre>

    <h3>Réponse 200</h3>
    <pre><code>{`{ "ok": true, "conversation_id": 142, "bot_enabled": 0 }`}</code></pre>

    <TryIt
      apiKey={apiKey}
      path="/v1/whatsapp/conversations/:id/bot"
      method="POST"
      pathParams={[{ key: 'id', label: 'id (conversation)', placeholder: '142' }]}
      defaults={{ enabled: '0' }}
      schema={[{ key: 'enabled', label: 'enabled (0 ou 1)', placeholder: '0', required: true }]}
    />
  </>
);

const SectionWAWhButtons = () => (
  <>
    <h1>Webhook : click sur un bouton de template</h1>
    <p>Lorsqu'un utilisateur clique sur un bouton <strong>QUICK_REPLY</strong> dans un template WhatsApp, Vocal POSTe un payload JSON vers le <code>webhook_url</code> que vous avez configuré sur ce template (UI <code>my.helvia.app</code> ou via <code>PUT /my/whatsapp-templates/:id/webhook</code>).</p>

    <div className="endpoint"><span className="badge badge-post">POST</span><code>{`<votre webhook_url>`}</code></div>

    <h2>Headers</h2>
    <ul>
      <li><code>Content-Type: application/json</code></li>
      <li><code>User-Agent: Vocal/1.0</code></li>
      <li><code>X-Vocal-Signature</code> : présent uniquement si vous avez configuré un <code>webhook_secret</code>. Format : HMAC-SHA256(secret, body) en hex lowercase.</li>
    </ul>

    <h2>Payload</h2>
    <pre><code>{`{
  "event": "whatsapp.button_clicked",
  "template_id": 17,
  "template_name": "rappel_vol_v1",
  "line_id": 22,
  "line_phone": "+41225391450",
  "conversation_id": 142,
  "from": "+41783020487",         // utilisateur qui a cliqué
  "to":   "+41225391450",         // votre ligne
  "message_sid": "SM...",
  "button": {
    "text": "Confirmer",
    "payload": "CONFIRM_VOL",
    "type": "QUICK_REPLY"
  },
  "original_body": "Bonjour Yannick…",  // texte du message contenant le bouton
  "timestamp": "2026-05-29T15:49:12.000Z"
}`}</code></pre>

    <h2>Vérifier la signature (Node.js)</h2>
    <pre><code>{`import crypto from 'node:crypto';

function verifyVocalSignature(rawBody, headerSig, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected, 'hex'),
    Buffer.from(headerSig, 'hex')
  );
}`}</code></pre>

    <div className="callout callout-info">
      <strong>Réponse attendue.</strong> Tout statut HTTP <code>2xx</code> est considéré comme un succès. Vocal n'effectue <strong>pas</strong> de retry automatique pour le moment ; loggez les <code>4xx/5xx</code> côté Vocal (table <code>whatsapp_button_events</code>, visible dans l'admin).
    </div>

    <h2>Tester</h2>
    <ol>
      <li>Configurez <code>webhook_url</code> + <code>webhook_secret</code> sur un template avec au moins un bouton <code>QUICK_REPLY</code>.</li>
      <li>Envoyez le template à votre numéro de test (<code>POST /v1/messages/whatsapp/template</code>).</li>
      <li>Cliquez le bouton sur WhatsApp → vous recevez le POST sur votre URL.</li>
    </ol>
  </>
);

const SectionSdk = () => (
  <>
    <h1>Exemples</h1>
    <h2>Node.js</h2>
    <pre><code>{`const resp = await fetch('${API_BASE}/v1/messages/whatsapp/template', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.VOCAL_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    from: '+41225394800',
    to: '+41XXXXXXXXX',
    template: 'rey_confirmation_v1',
    variables: { 1: 'Yannick', 2: '10:30' },
  }),
});
const data = await resp.json();
console.log(data);`}</code></pre>

    <h2>Python</h2>
    <pre><code>{`import requests, os
r = requests.post(
  '${API_BASE}/v1/messages/whatsapp/template',
  headers={'Authorization': f'Bearer {os.environ["VOCAL_API_KEY"]}'},
  json={
    'from': '+41225394800',
    'to': '+41XXXXXXXXX',
    'template': 'rey_confirmation_v1',
    'variables': {'1': 'Yannick', '2': '10:30'},
  },
)
print(r.status_code, r.json())`}</code></pre>

    <h2>PHP</h2>
    <pre><code>{`<?php
$ch = curl_init('${API_BASE}/v1/messages/whatsapp/template');
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_POST => true,
  CURLOPT_HTTPHEADER => [
    'Authorization: Bearer ' . getenv('VOCAL_API_KEY'),
    'Content-Type: application/json',
  ],
  CURLOPT_POSTFIELDS => json_encode([
    'from' => '+41225394800',
    'to' => '+41XXXXXXXXX',
    'template' => 'rey_confirmation_v1',
    'variables' => ['1' => 'Yannick', '2' => '10:30'],
  ]),
]);
$resp = curl_exec($ch);
echo $resp;`}</code></pre>
  </>
);

const SectionChangelog = () => (
  <>
    <h1>Changelog</h1>
    <h3>30 mai 2026</h3>
    <ul>
      <li>Inbox WhatsApp publique : <code>GET /v1/whatsapp/conversations</code>, <code>GET /v1/whatsapp/conversations/:id/messages</code>, <code>POST /v1/whatsapp/conversations/:id/reply</code>, <code>POST /v1/whatsapp/conversations/:id/bot</code>.</li>
      <li>Envoi free-form : <code>POST /v1/messages/whatsapp</code> (fenêtre 24h).</li>
      <li>Webhook documenté : <code>whatsapp.button_clicked</code> (signature HMAC-SHA256).</li>
      <li>Nouveau guide « Recréer l'inbox dans son app ».</li>
    </ul>
    <h3>27 mai 2026</h3>
    <ul>
      <li>Premier endpoint public : <code>POST /v1/messages/whatsapp/template</code>.</li>
      <li>Documentation interactive : <strong>docs.helvia.app</strong>.</li>
    </ul>
  </>
);

// ============================================================================
// App principale.
// ============================================================================
const App = () => {
  const auth = useAuth();
  const [active, setActive] = useState(() => {
    const h = (location.hash || '#intro').replace('#', '');
    return SECTIONS.flatMap(s => s.items).some(i => i.id === h) ? h : 'intro';
  });
  useEffect(() => {
    const onHash = () => {
      const h = (location.hash || '#intro').replace('#', '');
      if (SECTIONS.flatMap(s => s.items).some(i => i.id === h)) setActive(h);
    };
    window.addEventListener('hashchange', onHash);
    return () => window.removeEventListener('hashchange', onHash);
  }, []);

  const navigate = (id) => { location.hash = '#' + id; setActive(id); window.scrollTo(0, 0); };

  // Defaults pour Try-it : on tente d'extraire la première ligne WhatsApp + template approuvé du user.
  const [defaults, setDefaults] = useState({ from: '', template: '' });
  useEffect(() => {
    (async () => {
      if (!auth.myToken) return;
      try {
        const lr = await fetch(`${API_BASE}/my/lines`, { headers: { Authorization: `Bearer ${auth.myToken}` } });
        if (lr.ok) {
          const ld = await lr.json();
          const waLine = (ld.lines || []).find(l => l.wa_bot_enabled || l.whatsapp_enabled);
          if (waLine) setDefaults(d => ({ ...d, from: waLine.phone_number }));
        }
      } catch (e) {}
    })();
  }, [auth.myToken]);

  return (
    <div className="layout">
      <Sidebar active={active} onNavigate={navigate} />
      <main className="content">
        {active === 'intro'             && <SectionIntro />}
        {active === 'auth'              && <SectionAuth />}
        {active === 'errors'            && <SectionErrors />}
        {active === 'recreate'          && <SectionRecreate />}
        {active === 'wa-template'       && <SectionWATemplate apiKey={auth.apiKey} defaultFrom={defaults.from} defaultTemplate={defaults.template} />}
        {active === 'wa-freeform'       && <SectionWAFreeForm apiKey={auth.apiKey} defaultFrom={defaults.from} />}
        {active === 'wa-conv-list'      && <SectionWAConvList apiKey={auth.apiKey} />}
        {active === 'wa-conv-messages'  && <SectionWAConvMessages apiKey={auth.apiKey} />}
        {active === 'wa-conv-reply'     && <SectionWAConvReply apiKey={auth.apiKey} />}
        {active === 'wa-conv-bot'       && <SectionWAConvBot apiKey={auth.apiKey} />}
        {active === 'wa-wh-buttons'     && <SectionWAWhButtons />}
        {active === 'sdk'               && <SectionSdk />}
        {active === 'changelog'         && <SectionChangelog />}
      </main>
      <RightRail auth={auth} />
    </div>
  );
};

ReactDOM.createRoot(document.getElementById('app')).render(<App />);
