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

const API_URL = window.location.hostname === 'localhost' ? 'http://localhost:3000' : '';

// Messages d'erreur utilisateur
const ERROR_MESSAGES = {
  'Identifiants incorrects': 'Identifiant ou mot de passe erroné',
  'Utilisateur existant': 'Nom d\'utilisateur déjà pris',
  'Mot de passe incorrect': 'Mot de passe actuel incorrect',
  'Mot de passe actuel requis': 'Veuillez entrer votre mot de passe actuel',
  'Tous les champs requis': 'Veuillez remplir tous les champs',
  'GENERIC_ERROR': 'Une erreur est survenue, veuillez contacter l\'admin'
};

// Fonction pour traduire les erreurs serveur en messages utilisateur
const getErrorMessage = (serverError, isAuthError = false) => {
  if (ERROR_MESSAGES[serverError]) {
    return ERROR_MESSAGES[serverError];
  }
  // Pour les erreurs d'authentification non reconnues, afficher un message générique d'auth
  if (isAuthError) {
    return 'Identifiant ou mot de passe erroné';
  }
  return ERROR_MESSAGES['GENERIC_ERROR'];
};

// API Helper
const api = {
  async request(endpoint, options = {}) {
    const token = localStorage.getItem('token');
    const headers = {
      'Content-Type': 'application/json',
      ...(token && { Authorization: `Bearer ${token}` })
    };

    let response;
    try {
      response = await fetch(`${API_URL}${endpoint}`, {
        ...options,
        headers: { ...headers, ...options.headers }
      });
    } catch (e) {
      // Erreur réseau (pas de connexion, serveur down, etc.)
      throw new Error(ERROR_MESSAGES['GENERIC_ERROR']);
    }

    if (!response.ok) {
      let errorMessage = null;
      try {
        const errorData = await response.json();
        errorMessage = errorData.error;
      } catch (e) {
        // La réponse n'est pas du JSON valide
        errorMessage = null;
      }

      // Déterminer si c'est une erreur d'authentification
      const isAuthEndpoint = endpoint === '/api/login' || endpoint === '/api/register';

      // Auto-logout si le token est expiré ou invalide (sauf sur les endpoints d'auth)
      if (!isAuthEndpoint && (response.status === 401 || response.status === 403)) {
        localStorage.removeItem('token');
        localStorage.removeItem('user');
        window.location.reload();
        return;
      }

      // Créer une erreur avec le message original du serveur (sera traduit plus tard)
      const error = new Error(errorMessage || 'UNKNOWN');
      error.serverMessage = errorMessage;
      error.isAuthError = isAuthEndpoint;
      error.status = response.status;
      throw error;
    }

    try {
      return await response.json();
    } catch (e) {
      // La réponse OK n'est pas du JSON valide
      throw new Error(ERROR_MESSAGES['GENERIC_ERROR']);
    }
  },
  
  login: (username, password) => api.request('/api/login', {
    method: 'POST',
    body: JSON.stringify({ username, password })
  }),
  
  register: (username, email, password) => api.request('/api/register', {
    method: 'POST',
    body: JSON.stringify({ username, email, password })
  }),
  
  getExercises: () => api.request('/api/exercises'),
  createExercise: (data) => api.request('/api/exercises', {
    method: 'POST',
    body: JSON.stringify(data)
  }),
  updateExercise: (id, data) => api.request(`/api/exercises/${id}`, {
    method: 'PUT',
    body: JSON.stringify(data)
  }),
  deleteExercise: (id) => api.request(`/api/exercises/${id}`, { method: 'DELETE' }),
  setExerciseHidden: (id, hidden) => api.request(`/api/exercises/${id}/hidden`, {
    method: 'PUT',
    body: JSON.stringify({ hidden })
  }),

  getPerformances: () => api.request('/api/performances'),
  createPerformance: (data) => api.request('/api/performances', {
    method: 'POST',
    body: JSON.stringify(data)
  }),
  updatePerformance: (id, data) => api.request(`/api/performances/${id}`, {
    method: 'PUT',
    body: JSON.stringify(data)
  }),
  deletePerformance: (id) => api.request(`/api/performances/${id}`, { method: 'DELETE' }),
  
  getCategories: () => api.request('/api/categories'),
  createCategory: (data) => api.request('/api/categories', {
    method: 'POST',
    body: JSON.stringify(data)
  }),
  updateCategory: (id, data) => api.request(`/api/categories/${id}`, {
    method: 'PUT',
    body: JSON.stringify(data)
  }),
  deleteCategory: (id) => api.request(`/api/categories/${id}`, { method: 'DELETE' }),
  setCategoryHidden: (id, hidden) => api.request(`/api/categories/${id}/hidden`, {
    method: 'PUT',
    body: JSON.stringify({ hidden })
  }),
  reorderCategories: (order) => api.request('/api/categories/reorder', {
    method: 'PUT',
    body: JSON.stringify({ order })
  }),

  getStats: () => api.request('/api/stats'),
  getProfile: () => api.request('/api/user/profile'),
  updateProfile: (data) => api.request('/api/user/profile', {
    method: 'PUT',
    body: JSON.stringify(data)
  }),
  deleteAccount: () => api.request('/api/user/account', { method: 'DELETE' }),

  getWeights: () => api.request('/api/weights'),
  addWeight: (weight) => api.request('/api/weights', {
    method: 'POST',
    body: JSON.stringify({ weight })
  }),
  updateWeight: (id, weight) => api.request(`/api/weights/${id}`, {
    method: 'PUT',
    body: JSON.stringify({ weight })
  }),
  deleteWeight: (id) => api.request(`/api/weights/${id}`, { method: 'DELETE' }),

  // Feedbacks
  sendFeedback: (message) => api.request('/api/feedbacks', {
    method: 'POST',
    body: JSON.stringify({ message })
  }),

  // Admin
  getAdminUsers: () => api.request('/api/admin/users'),
  getAdminFeedbacks: () => api.request('/api/admin/feedbacks'),
  deleteAdminFeedback: (id) => api.request(`/api/admin/feedbacks/${id}`, { method: 'DELETE' }),
  deleteAdminUser: (id) => api.request(`/api/admin/users/${id}`, { method: 'DELETE' }),

  // Password reset
  forgotPassword: (email) => api.request('/api/forgot-password', {
    method: 'POST',
    body: JSON.stringify({ email })
  }),
  verifyResetToken: (token) => api.request(`/api/verify-reset-token/${token}`),
  resetPassword: (token, newPassword) => api.request('/api/reset-password', {
    method: 'POST',
    body: JSON.stringify({ token, newPassword })
  })
};

// Format helpers
const formatDate = (dateString, format = 'full', timezone = 'Europe/Paris') => {
  try {
    // Si la date n'a pas de timezone (format SQLite), on ajoute 'Z' pour la traiter comme UTC
    let dateStr = dateString;
    if (dateStr && !dateStr.includes('Z') && !dateStr.includes('+') && !dateStr.includes('T')) {
      // Format SQLite: "YYYY-MM-DD HH:MM:SS" -> on le convertit en ISO avec Z
      dateStr = dateStr.replace(' ', 'T') + 'Z';
    }

    const date = new Date(dateStr);
    if (format === 'short') {
      return date.toLocaleDateString('fr-FR', {
        day: '2-digit',
        month: '2-digit',
        timeZone: timezone
      });
    }
    return date.toLocaleDateString('fr-FR', {
      day: '2-digit',
      month: 'long',
      year: 'numeric',
      hour: '2-digit',
      minute: '2-digit',
      timeZone: timezone
    });
  } catch (error) {
    console.error('Error in formatDate:', error);
    // Fallback: utiliser la date sans timezone
    const date = new Date(dateString);
    if (format === 'short') {
      return date.toLocaleDateString('fr-FR', {
        day: '2-digit',
        month: '2-digit'
      });
    }
    return date.toLocaleDateString('fr-FR', {
      day: '2-digit',
      month: 'long',
      year: 'numeric',
      hour: '2-digit',
      minute: '2-digit'
    });
  }
};

// Helper pour formater une date avec timezone spécifique
const formatDateWithTimezone = (dateString, options = {}, timezone = 'Europe/Paris') => {
  try {
    // Si la date n'a pas de timezone (format SQLite), on ajoute 'Z' pour la traiter comme UTC
    let dateStr = dateString;
    if (dateStr && !dateStr.includes('Z') && !dateStr.includes('+') && !dateStr.includes('T')) {
      dateStr = dateStr.replace(' ', 'T') + 'Z';
    }

    const date = new Date(dateStr);
    return date.toLocaleDateString('fr-FR', {
      ...options,
      timeZone: timezone
    });
  } catch (error) {
    console.error('Error in formatDateWithTimezone:', error);
    // Fallback: utiliser la date sans timezone
    const date = new Date(dateString);
    return date.toLocaleDateString('fr-FR', options);
  }
};

// Helper pour obtenir la date du jour au format YYYY-MM-DD dans une timezone spécifique
const getTodayInTimezone = (timezone = 'Europe/Paris') => {
  try {
    const now = new Date();
    const formatter = new Intl.DateTimeFormat('en-CA', {
      timeZone: timezone,
      year: 'numeric',
      month: '2-digit',
      day: '2-digit'
    });
    return formatter.format(now); // Format: YYYY-MM-DD
  } catch (error) {
    console.error('Error in getTodayInTimezone:', error);
    // Fallback: utiliser la date locale au format ISO
    const now = new Date();
    return now.toISOString().split('T')[0];
  }
};

const formatTime = (seconds) => {
  const h = Math.floor(seconds / 3600);
  const m = Math.floor((seconds % 3600) / 60);
  const s = Math.floor(seconds % 60);

  if (h > 0) {
    return `${h}h ${m}min ${s}s`;
  } else if (m > 0) {
    return `${m}min ${s}s`;
  } else {
    return `${s}s`;
  }
};

const formatValue = (value, unit, weight, series) => {
  // Si mode séries multiples, afficher les séries
  if (series) {
    try {
      const seriesArray = typeof series === 'string' ? JSON.parse(series) : series;
      if (seriesArray && seriesArray.length > 0) {
        const formattedSeries = seriesArray.map(s => {
          const repsStr = s.reps || '0';
          const weightStr = s.weight ? `×${s.weight}kg` : '';
          return `${repsStr}${weightStr}`;
        }).join(' • ');
        return formattedSeries;
      }
    } catch (e) {
      // Si erreur de parsing, continuer avec le format simple
    }
  }

  // Format simple
  let result;
  if (unit === 'temps') {
    result = formatTime(value);
  } else {
    result = `${value} ${unit}`;
  }
  if (weight) result += ` × ${weight} kg`;
  return result;
};

// Helper pour calculer le volume d'une performance (avec ou sans séries)
const calculatePerformanceVolume = (perf) => {
  // Si séries multiples, calculer la somme des volumes de toutes les séries
  if (perf.series) {
    try {
      const seriesArray = typeof perf.series === 'string' ? JSON.parse(perf.series) : perf.series;
      if (seriesArray && seriesArray.length > 0) {
        return seriesArray.reduce((total, s) => {
          const reps = parseFloat(s.reps) || 0;
          const weight = parseFloat(s.weight) || 0;
          return total + (weight > 0 ? reps * weight : reps);
        }, 0);
      }
    } catch (e) {
      // En cas d'erreur, utiliser la méthode simple
    }
  }

  // Format simple: volume = reps × poids (ou juste reps si pas de poids)
  return perf.weight ? perf.value * perf.weight : perf.value;
};

// Connection Status
function ConnectionIndicator() {
  const [online, setOnline] = useState(navigator.onLine);

  useEffect(() => {
    const updateStatus = () => {
      const isOnline = navigator.onLine;
      setOnline(isOnline);
      // Synchroniser aussi l'indicateur HTML natif
      const htmlIndicator = document.getElementById('offline-indicator');
      if (htmlIndicator) {
        htmlIndicator.style.display = isOnline ? 'none' : 'flex';
      }
    };

    window.addEventListener('online', updateStatus);
    window.addEventListener('offline', updateStatus);

    // Vérification périodique toutes les 5 secondes
    const interval = setInterval(updateStatus, 5000);

    return () => {
      window.removeEventListener('online', updateStatus);
      window.removeEventListener('offline', updateStatus);
      clearInterval(interval);
    };
  }, []);

  if (online) return null;

  return (
    <div className="connection-status connection-offline">
      Hors ligne
    </div>
  );
}

// PWA Install Prompt
function PWAInstallPrompt({ show, onClose }) {
  const [dontShowAgain, setDontShowAgain] = useState(false);
  const [platform, setPlatform] = useState('unknown');

  useEffect(() => {
    // Détecter la plateforme
    const userAgent = navigator.userAgent || navigator.vendor || window.opera;

    if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) {
      setPlatform('ios');
    } else if (/android/i.test(userAgent)) {
      setPlatform('android');
    } else {
      setPlatform('desktop');
    }
  }, []);

  const handleClose = () => {
    if (dontShowAgain) {
      localStorage.setItem('pwa-install-dismissed', 'true');
    }
    onClose();
  };

  if (!show) return null;

  return (
    <div className="modal-overlay" style={{ zIndex: 10000 }}>
      <div className="modal fade-in" style={{ maxWidth: '500px' }}>
        <div className="modal-header">
          📱 Installer l'application
        </div>

        <div style={{ padding: '20px', fontSize: '14px', lineHeight: '1.6' }}>
          <div style={{ marginBottom: '20px', color: 'var(--chalk-soft)' }}>
            Pour une meilleure expérience, installez Fitness Tracker sur votre écran d'accueil !
          </div>

          {platform === 'ios' && (
            <div className="install-instruction-box" style={{ background: 'var(--floor-3)', padding: '16px', borderRadius: '8px', marginBottom: '16px' }}>
              <h4 style={{ fontWeight: '600', marginBottom: '12px', color: 'var(--chalk)' }}>
                📱 Sur iPhone/iPad :
              </h4>
              <ol style={{ margin: 0, paddingLeft: '20px', color: 'var(--chalk-soft)' }}>
                <li style={{ marginBottom: '8px' }}>Appuyez sur le bouton <strong>Partager</strong> <span style={{ fontSize: '18px' }}>⎙</span> en bas de Safari</li>
                <li style={{ marginBottom: '8px' }}>Faites défiler et sélectionnez <strong>"Sur l'écran d'accueil"</strong> 📲</li>
                <li style={{ marginBottom: '8px' }}>Appuyez sur <strong>"Ajouter"</strong></li>
              </ol>
            </div>
          )}

          {platform === 'android' && (
            <div className="install-instruction-box" style={{ background: 'var(--floor-3)', padding: '16px', borderRadius: '8px', marginBottom: '16px' }}>
              <h4 style={{ fontWeight: '600', marginBottom: '12px', color: 'var(--chalk)' }}>
                📱 Sur Android :
              </h4>
              <ol style={{ margin: 0, paddingLeft: '20px', color: 'var(--chalk-soft)' }}>
                <li style={{ marginBottom: '8px' }}>Appuyez sur le menu <strong>⋮</strong> (trois points) en haut à droite</li>
                <li style={{ marginBottom: '8px' }}>Sélectionnez <strong>"Ajouter à l'écran d'accueil"</strong> ou <strong>"Installer l'application"</strong></li>
                <li style={{ marginBottom: '8px' }}>Appuyez sur <strong>"Ajouter"</strong> ou <strong>"Installer"</strong></li>
              </ol>
            </div>
          )}

          {platform === 'desktop' && (
            <div className="install-instruction-box" style={{ background: 'var(--floor-3)', padding: '16px', borderRadius: '8px', marginBottom: '16px', color: 'var(--chalk-soft)' }}>
              💻 Consultez cette application depuis votre mobile pour l'installer sur votre écran d'accueil.
            </div>
          )}

          <div style={{
            display: 'flex',
            alignItems: 'center',
            gap: '8px',
            padding: '12px',
            background: 'var(--yellow-wash)',
            borderRadius: '6px',
            marginBottom: '16px'
          }}>
            <span style={{ fontSize: '16px' }}>💡</span>
            <span style={{ fontSize: '13px', color: 'var(--yellow-ink)' }}>
              L'application fonctionnera comme une app native et sera accessible hors ligne !
            </span>
          </div>

          <label style={{
            display: 'flex',
            alignItems: 'center',
            gap: '8px',
            cursor: 'pointer',
            marginBottom: '16px',
            fontSize: '14px'
          }}>
            <input
              type="checkbox"
              checked={dontShowAgain}
              onChange={(e) => setDontShowAgain(e.target.checked)}
              style={{ cursor: 'pointer' }}
            />
            <span>Ne plus afficher ce message</span>
          </label>

          <div style={{ display: 'flex', gap: '12px' }}>
            <button
              onClick={handleClose}
              className="btn btn-primary"
              style={{ flex: 1 }}
            >
              J'ai compris
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

// Login Screen
function LoginScreen({ onLogin }) {
  const [isLogin, setIsLogin] = useState(true);
  const [showForgotPassword, setShowForgotPassword] = useState(false);
  const [username, setUsername] = useState('');
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState('');
  const [loading, setLoading] = useState(false);

  // Captcha states
  const [failedAttempts, setFailedAttempts] = useState(0);
  const [captchaNumbers, setCaptchaNumbers] = useState({ a: 0, b: 0 });
  const [captchaAnswer, setCaptchaAnswer] = useState('');

  // Password reset states
  const [forgotEmail, setForgotEmail] = useState('');
  const [forgotLoading, setForgotLoading] = useState(false);
  const [forgotSuccess, setForgotSuccess] = useState(false);
  const [forgotError, setForgotError] = useState('');

  // Reset password modal states (when user clicks on email link)
  const [showResetPassword, setShowResetPassword] = useState(false);
  const [resetToken, setResetToken] = useState('');
  const [newPassword, setNewPassword] = useState('');
  const [confirmPassword, setConfirmPassword] = useState('');
  const [resetLoading, setResetLoading] = useState(false);
  const [resetSuccess, setResetSuccess] = useState(false);
  const [resetError, setResetError] = useState('');

  // Générer un nouveau captcha
  const generateCaptcha = () => {
    const a = Math.floor(Math.random() * 10) + 1;
    const b = Math.floor(Math.random() * 10) + 1;
    setCaptchaNumbers({ a, b });
    setCaptchaAnswer('');
  };

  // Générer captcha au montage et quand on change de mode
  useEffect(() => {
    generateCaptcha();
  }, [isLogin]);

  // Vérifier si l'URL contient un token de réinitialisation
  useEffect(() => {
    const urlParams = new URLSearchParams(window.location.search);
    const token = urlParams.get('reset');
    if (token) {
      // Vérifier la validité du token
      api.verifyResetToken(token)
        .then(() => {
          setResetToken(token);
          setShowResetPassword(true);
          // Nettoyer l'URL
          window.history.replaceState({}, document.title, window.location.pathname);
        })
        .catch(() => {
          setError('Le lien de réinitialisation est invalide ou a expiré.');
          window.history.replaceState({}, document.title, window.location.pathname);
        });
    }
  }, []);

  // Vérifier si le captcha doit être affiché
  const showCaptcha = !isLogin || failedAttempts >= 3;

  // Gérer la demande de réinitialisation de mot de passe
  const handleForgotPassword = async (e) => {
    e.preventDefault();
    setForgotError('');
    setForgotLoading(true);

    try {
      await api.forgotPassword(forgotEmail);
      setForgotSuccess(true);
    } catch (err) {
      setForgotError(err.message || 'Une erreur est survenue');
    } finally {
      setForgotLoading(false);
    }
  };

  // Gérer la réinitialisation du mot de passe (après clic sur le lien email)
  const handleResetPassword = async (e) => {
    e.preventDefault();
    setResetError('');

    if (newPassword !== confirmPassword) {
      setResetError('Les mots de passe ne correspondent pas');
      return;
    }

    if (newPassword.length < 6) {
      setResetError('Le mot de passe doit contenir au moins 6 caractères');
      return;
    }

    setResetLoading(true);

    try {
      await api.resetPassword(resetToken, newPassword);
      setResetSuccess(true);
    } catch (err) {
      setResetError(err.message || 'Une erreur est survenue');
    } finally {
      setResetLoading(false);
    }
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    setError('');

    // Vérifier le captcha si nécessaire
    if (showCaptcha) {
      const expectedAnswer = captchaNumbers.a + captchaNumbers.b;
      if (parseInt(captchaAnswer) !== expectedAnswer) {
        setError('Captcha incorrect');
        generateCaptcha();
        return;
      }
    }

    setLoading(true);

    try {
      let response;
      if (isLogin) {
        response = await api.login(username, password);
      } else {
        response = await api.register(username, email, password);
      }

      localStorage.setItem('token', response.token);
      localStorage.setItem('user', JSON.stringify(response.user));
      setFailedAttempts(0);
      onLogin(response.user);
    } catch (err) {
      // Traduire l'erreur serveur en message utilisateur
      const userMessage = getErrorMessage(err.serverMessage || err.message, err.isAuthError);
      setError(userMessage);

      // Incrémenter les tentatives échouées pour le login
      if (isLogin) {
        setFailedAttempts(prev => prev + 1);
      }
      generateCaptcha();
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="login-container">
      <div className="login-card fade-in">
        <h1 className="login-title">Fitness Tracker</h1>
        
        {error && (
          <div style={{
            background: '#FEE2E2',
            color: '#DC2626',
            padding: '12px',
            borderRadius: '12px',
            marginBottom: '20px',
            fontSize: '14px',
            textAlign: 'center'
          }}>
            {error}
          </div>
        )}

        <form onSubmit={handleSubmit}>
          <div className="form-group">
            <label className="form-label">{isLogin ? "Nom d'utilisateur ou email" : "Nom d'utilisateur"}</label>
            <input
              type="text"
              className="form-input"
              value={username}
              onChange={(e) => setUsername(e.target.value)}
              placeholder={isLogin ? "Nom d'utilisateur ou email" : "Votre nom d'utilisateur"}
              required
            />
          </div>

          {!isLogin && (
            <div className="form-group">
              <label className="form-label">Email</label>
              <input
                type="email"
                className="form-input"
                value={email}
                onChange={(e) => setEmail(e.target.value)}
                placeholder="email@example.com"
                required
              />
            </div>
          )}

          <div className="form-group">
            <label className="form-label">Mot de passe</label>
            <input
              type="password"
              className="form-input"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
              placeholder="Votre mot de passe"
              required
            />
          </div>

          {isLogin && (
            <p style={{ textAlign: 'right', marginTop: '-8px', marginBottom: '16px' }}>
              <span
                className="login-link"
                onClick={() => setShowForgotPassword(true)}
                style={{ fontSize: '13px' }}
              >
                Mot de passe oublié ?
              </span>
            </p>
          )}

          {showCaptcha && (
            <div className="form-group">
              <label className="form-label" style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
                🤖 Vérification
                {isLogin && failedAttempts >= 3 && (
                  <span className="text-secondary" style={{ fontSize: '11px', color: '#6B7280', fontWeight: 'normal' }}>
                    (3 tentatives échouées)
                  </span>
                )}
              </label>
              <div style={{
                display: 'flex',
                alignItems: 'center',
                gap: '12px',
                background: '#F3F4F6',
                padding: '12px 16px',
                borderRadius: '12px',
                marginBottom: '8px'
              }}>
                <span style={{
                  fontSize: '18px',
                  fontWeight: '700',
                  color: '#1A1A1A',
                  fontFamily: 'monospace',
                  letterSpacing: '2px'
                }}>
                  {captchaNumbers.a} + {captchaNumbers.b} = ?
                </span>
                <button
                  type="button"
                  onClick={generateCaptcha}
                  style={{
                    background: 'none',
                    border: 'none',
                    cursor: 'pointer',
                    fontSize: '16px',
                    padding: '4px'
                  }}
                  title="Nouveau captcha"
                >
                  🔄
                </button>
              </div>
              <input
                type="number"
                className="form-input"
                value={captchaAnswer}
                onChange={(e) => setCaptchaAnswer(e.target.value)}
                placeholder="Votre réponse"
                required
                style={{ textAlign: 'center', fontSize: '18px', fontWeight: '600' }}
              />
            </div>
          )}

          <button
            type="submit"
            className="btn btn-primary"
            style={{ width: '100%', justifyContent: 'center', marginTop: '16px' }}
            disabled={loading}
          >
            {loading ? 'Chargement...' : (isLogin ? 'Se connecter' : 'S\'inscrire')}
          </button>
        </form>

        <p className="login-subtitle">
          {isLogin ? "Pas encore de compte ? " : "Déjà un compte ? "}
          <span className="login-link" onClick={() => { setIsLogin(!isLogin); setFailedAttempts(0); setError(''); }}>
            {isLogin ? "S'inscrire" : "Se connecter"}
          </span>
        </p>
      </div>

      {showForgotPassword && (
        <div className="modal-overlay" onClick={() => { setShowForgotPassword(false); setForgotSuccess(false); setForgotError(''); setForgotEmail(''); }}>
          <div className="modal fade-in" onClick={(e) => e.stopPropagation()} style={{ maxWidth: '400px' }}>
            <div className="modal-header">Mot de passe oublié</div>

            {forgotSuccess ? (
              <div>
                <div style={{
                  background: '#D1FAE5',
                  color: '#065F46',
                  padding: '16px',
                  borderRadius: '12px',
                  marginBottom: '20px',
                  textAlign: 'center',
                  lineHeight: '1.6'
                }}>
                  <p style={{ fontWeight: '600', marginBottom: '8px' }}>Email envoyé !</p>
                  <p style={{ fontSize: '14px' }}>
                    Si un compte est associé à cette adresse email, vous recevrez un lien pour réinitialiser votre mot de passe.
                  </p>
                </div>
                <button
                  type="button"
                  className="btn btn-primary"
                  onClick={() => { setShowForgotPassword(false); setForgotSuccess(false); setForgotEmail(''); }}
                  style={{ width: '100%', justifyContent: 'center' }}
                >
                  Fermer
                </button>
              </div>
            ) : (
              <form onSubmit={handleForgotPassword}>
                <p style={{ marginBottom: '16px', color: '#5F6368', lineHeight: '1.6' }}>
                  Entrez l'adresse email associée à votre compte. Nous vous enverrons un lien pour réinitialiser votre mot de passe.
                </p>

                {forgotError && (
                  <div style={{
                    background: '#FEE2E2',
                    color: '#DC2626',
                    padding: '12px',
                    borderRadius: '12px',
                    marginBottom: '16px',
                    fontSize: '14px',
                    textAlign: 'center'
                  }}>
                    {forgotError}
                  </div>
                )}

                <div className="form-group">
                  <label className="form-label">Adresse email</label>
                  <input
                    type="email"
                    className="form-input"
                    value={forgotEmail}
                    onChange={(e) => setForgotEmail(e.target.value)}
                    placeholder="email@example.com"
                    required
                  />
                </div>

                <div className="form-actions" style={{ marginTop: '20px' }}>
                  <button
                    type="button"
                    className="btn"
                    onClick={() => { setShowForgotPassword(false); setForgotError(''); setForgotEmail(''); }}
                  >
                    Annuler
                  </button>
                  <button
                    type="submit"
                    className="btn btn-primary"
                    disabled={forgotLoading}
                  >
                    {forgotLoading ? 'Envoi...' : 'Envoyer le lien'}
                  </button>
                </div>
              </form>
            )}
          </div>
        </div>
      )}

      {showResetPassword && (
        <div className="modal-overlay">
          <div className="modal fade-in" onClick={(e) => e.stopPropagation()} style={{ maxWidth: '400px' }}>
            <div className="modal-header">Réinitialiser le mot de passe</div>

            {resetSuccess ? (
              <div>
                <div style={{
                  background: '#D1FAE5',
                  color: '#065F46',
                  padding: '16px',
                  borderRadius: '12px',
                  marginBottom: '20px',
                  textAlign: 'center',
                  lineHeight: '1.6'
                }}>
                  <p style={{ fontWeight: '600', marginBottom: '8px' }}>Mot de passe modifié !</p>
                  <p style={{ fontSize: '14px' }}>
                    Votre mot de passe a été réinitialisé avec succès. Vous pouvez maintenant vous connecter.
                  </p>
                </div>
                <button
                  type="button"
                  className="btn btn-primary"
                  onClick={() => { setShowResetPassword(false); setResetSuccess(false); setNewPassword(''); setConfirmPassword(''); }}
                  style={{ width: '100%', justifyContent: 'center' }}
                >
                  Se connecter
                </button>
              </div>
            ) : (
              <form onSubmit={handleResetPassword}>
                <p style={{ marginBottom: '16px', color: '#5F6368', lineHeight: '1.6' }}>
                  Entrez votre nouveau mot de passe.
                </p>

                {resetError && (
                  <div style={{
                    background: '#FEE2E2',
                    color: '#DC2626',
                    padding: '12px',
                    borderRadius: '12px',
                    marginBottom: '16px',
                    fontSize: '14px',
                    textAlign: 'center'
                  }}>
                    {resetError}
                  </div>
                )}

                <div className="form-group">
                  <label className="form-label">Nouveau mot de passe</label>
                  <input
                    type="password"
                    className="form-input"
                    value={newPassword}
                    onChange={(e) => setNewPassword(e.target.value)}
                    placeholder="Minimum 6 caractères"
                    required
                    minLength={6}
                  />
                </div>

                <div className="form-group">
                  <label className="form-label">Confirmer le mot de passe</label>
                  <input
                    type="password"
                    className="form-input"
                    value={confirmPassword}
                    onChange={(e) => setConfirmPassword(e.target.value)}
                    placeholder="Confirmer le mot de passe"
                    required
                  />
                </div>

                <button
                  type="submit"
                  className="btn btn-primary"
                  style={{ width: '100%', justifyContent: 'center', marginTop: '16px' }}
                  disabled={resetLoading}
                >
                  {resetLoading ? 'Modification...' : 'Modifier le mot de passe'}
                </button>
              </form>
            )}
          </div>
        </div>
      )}
    </div>
  );
}

// ============================================
// SÉANCE « PLAQUES » (refonte du 2026-10-03)
// Accueil = la séance du jour : la dernière fois et le chiffre à battre pour chaque exercice.
// ============================================
const TZ_DEFAULT = 'Europe/Paris';
const REST_SECONDS = 120;
const WEEK_LETTERS = ['L', 'M', 'M', 'J', 'V', 'S', 'D'];

// Jour local (AAAA-MM-JJ) d'une date de la base, dans le fuseau de l'utilisateur
const dayOf = (iso, tz = TZ_DEFAULT) => {
  try {
    let s = iso;
    if (s && !s.includes('Z') && !s.includes('+') && !s.includes('T')) s = s.replace(' ', 'T') + 'Z';
    return new Intl.DateTimeFormat('en-CA', { timeZone: tz, year: 'numeric', month: '2-digit', day: '2-digit' }).format(new Date(s));
  } catch (e) {
    return (iso || '').slice(0, 10);
  }
};

const shiftDay = (day, n) => {
  const d = new Date(day + 'T12:00:00Z');
  d.setUTCDate(d.getUTCDate() + n);
  return d.toISOString().slice(0, 10);
};

const daysBetween = (from, to) => Math.round((new Date(to + 'T12:00:00Z') - new Date(from + 'T12:00:00Z')) / 86400000);
const dayFmt = (day, opts) => new Date(day + 'T12:00:00Z').toLocaleDateString('fr-FR', { ...opts, timeZone: 'UTC' });

// « aujourd'hui », « hier », « jeudi » (moins d'une semaine), sinon « le 25 septembre »
const whenLabel = (day, today) => {
  const gap = daysBetween(day, today);
  if (gap === 0) return "aujourd'hui";
  if (gap === 1) return 'hier';
  if (gap > 1 && gap < 7) return dayFmt(day, { weekday: 'long' });
  return 'le ' + dayFmt(day, { day: 'numeric', month: 'long' });
};

// Séries d'une performance ; une ancienne perf sans séries compte comme une série unique
const parseSets = (perf) => {
  if (!perf) return [];
  if (perf.series) {
    try {
      const arr = typeof perf.series === 'string' ? JSON.parse(perf.series) : perf.series;
      if (Array.isArray(arr) && arr.length > 0) {
        return arr.map(s => ({ reps: Number(s.reps) || 0, weight: Number(s.weight) || 0 }));
      }
    } catch (e) {
      // séries illisibles : on retombe sur la valeur simple
    }
  }
  return [{ reps: Number(perf.value) || 0, weight: Number(perf.weight) || 0 }];
};

const kg = (w) => String(Math.round(w * 100) / 100).replace('.', ',');
const setLabel = (s) => (s.weight ? `${s.reps}×${kg(s.weight)}` : `${s.reps}`);
const thousands = (n) => Math.round(n).toLocaleString('fr-FR').replace(/ /g, ' ');

// À battre : une répétition de plus sur la charge la plus lourde de la dernière fois
const targetOf = (sets) => {
  if (!sets.length) return null;
  const w = Math.max(...sets.map(s => s.weight || 0));
  const r = Math.max(...sets.filter(s => (s.weight || 0) === w).map(s => s.reps));
  return { reps: r + 1, weight: w };
};

const beats = (sets, target) => !!target && sets.some(s => s.weight > target.weight || (s.weight === target.weight && s.reps >= target.reps));

// Une série comparée à la même série de la dernière fois
const compareSet = (cur, ref) => {
  if (!ref) return null;
  if (cur.weight > ref.weight) return { tone: 'up', text: `${kg(cur.weight - ref.weight)} kg de plus que la dernière fois` };
  if (cur.weight < ref.weight) return { tone: 'down', text: `${kg(ref.weight - cur.weight)} kg de moins que la dernière fois` };
  const d = cur.reps - ref.reps;
  if (d > 0) return { tone: 'up', text: d === 1 ? 'Une répétition de plus que la dernière fois' : `${d} répétitions de plus que la dernière fois` };
  if (d < 0) return { tone: 'down', text: d === -1 ? 'Une répétition de moins que la dernière fois' : `${-d} répétitions de moins que la dernière fois` };
  return { tone: 'same', text: 'Comme la dernière fois' };
};

// Performances d'un exercice : celle du jour et la précédente (l'API les renvoie de la plus récente à la plus ancienne)
const exerciseState = (performances, exerciseId, today, tz) => {
  const perfs = performances.filter(p => p.exercise_id === exerciseId);
  const todayPerf = perfs.find(p => dayOf(p.performed_at, tz) === today) || null;
  const last = perfs.find(p => dayOf(p.performed_at, tz) !== today) || null;
  return { perfs, todayPerf, last };
};

// Exercices visibles rangés par catégorie visible, dans l'ordre propre à chaque catégorie
const groupByCategory = (exercises, categories) => {
  const hiddenNames = new Set(categories.filter(c => c.hidden).map(c => c.name));
  const groups = {};
  exercises.filter(ex => !ex.hidden).forEach(ex => {
    if (ex.categories && ex.categories.length > 0) {
      ex.categories.forEach(cat => {
        if (hiddenNames.has(cat.name)) return;
        (groups[cat.name] = groups[cat.name] || []).push({ ex, pos: cat.position || ex.position || 0 });
      });
    } else {
      (groups['Autre'] = groups['Autre'] || []).push({ ex, pos: ex.position || 0 });
    }
  });
  Object.keys(groups).forEach(k => { groups[k] = groups[k].sort((a, b) => a.pos - b.pos).map(g => g.ex); });
  return groups;
};

const ICON_PATHS = {
  refresh: 'M20 11a8 8 0 1 0-2.3 5.7M20 4v7h-7',
  stats: 'M4 20V10M10 20V4M16 20v-7M22 20H2',
  tag: 'M3 12V4h8l10 10-8 8L3 12zM7.5 7.5h.01',
  settings: 'M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z',
  more: 'M5 12h.01M12 12h.01M19 12h.01',
  check: 'M5 12l5 5 9-10',
  plus: 'M12 5v14M5 12h14',
  chevronDown: 'M6 9l6 6 6-6',
  chart: 'M3 17l6-6 4 4 8-8M15 7h6v6',
  star: 'M12 2l2.9 6.6 7.1.6-5.4 4.7 1.6 7L12 17.3 5.8 20.9l1.6-7L2 9.2l7.1-.6z',
  trash: 'M4 7h16M10 11v6M14 11v6M6 7l1 13h10l1-13M9 7V4h6v3',
  edit: 'M4 20h4L19 9l-4-4L4 16v4zM14 6l4 4',
  eye: 'M2 12s3.6-7 10-7 10 7 10 7-3.6 7-10 7S2 12 2 12zM12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z',
  eyeOff: 'M3 3l18 18M10.6 5.1A10 10 0 0 1 12 5c6.4 0 10 7 10 7a17 17 0 0 1-3.2 4.1M6.6 6.6A17 17 0 0 0 2 12s3.6 7 10 7a9.7 9.7 0 0 0 5.4-1.6M9.9 9.9a3 3 0 0 0 4.2 4.2',
  close: 'M6 6l12 12M18 6L6 18',
  camera: 'M4 8h3l2-3h6l2 3h3v11H4zM12 17a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7z',
  grip: 'M9 6h.01M15 6h.01M9 12h.01M15 12h.01M9 18h.01M15 18h.01',
};

function Icon({ name, size = 20 }) {
  const filled = name === 'star';
  return (
    <svg className="ico" width={size} height={size} viewBox="0 0 24 24" fill={filled ? 'currentColor' : 'none'} stroke={filled ? 'none' : 'currentColor'} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d={ICON_PATHS[name]} />
    </svg>
  );
}

const formatClock = (seconds) => {
  const h = Math.floor(seconds / 3600);
  const m = Math.floor((seconds % 3600) / 60);
  const s = seconds % 60;
  const mm = String(m).padStart(h > 0 ? 2 : 1, '0');
  return (h > 0 ? h + ':' : '') + mm + ':' + String(s).padStart(2, '0');
};

function TopActions({ onShowStats, onManageCategories, onShowProfile }) {
  return (
    <div className="top-actions">
      <button className="top-btn" onClick={() => {
        if ('caches' in window) caches.keys().then(names => names.forEach(name => caches.delete(name)));
        if (navigator.serviceWorker) navigator.serviceWorker.getRegistrations().then(regs => regs.forEach(r => r.update()));
        window.location.reload(true);
      }} title="Actualiser l'application" aria-label="Actualiser l'application"><Icon name="refresh" /></button>
      <button className="top-btn" onClick={onShowStats} title="Statistiques" aria-label="Statistiques"><Icon name="stats" /></button>
      <button className="top-btn" onClick={onManageCategories} title="Gérer les catégories" aria-label="Gérer les catégories"><Icon name="tag" /></button>
      <button className="top-btn" onClick={onShowProfile} title="Profil et réglages" aria-label="Profil et réglages"><Icon name="settings" /></button>
    </div>
  );
}

function SessionHome({ user, exercises, performances, categories, onOpenExercise, onToggleHidden, onToggleCategoryHidden, onAddExercise, onShowStats, onManageCategories, onShowProfile }) {
  const tz = user?.timezone || TZ_DEFAULT;
  const today = getTodayInTimezone(tz);
  const [openSection, setOpenSection] = useState({});

  const perfDay = useMemo(() => {
    const m = new Map();
    performances.forEach(p => m.set(p.id, dayOf(p.performed_at, tz)));
    return m;
  }, [performances, tz]);

  const groups = groupByCategory(exercises, categories);
  const visibleCategories = categories.filter(c => !c.hidden);
  const hiddenCategories = categories.filter(c => c.hidden);
  const hiddenExercises = exercises.filter(ex => ex.hidden);
  const plates = [
    ...visibleCategories.map(c => ({ name: c.name, color: c.color })),
    ...(groups['Autre'] ? [{ name: 'Autre', color: null }] : []),
  ];
  const colorOf = (name) => plates.find(p => p.name === name)?.color || null;

  const lastDayOf = (name) => {
    let last = null;
    (groups[name] || []).forEach(ex => {
      const p = performances.find(p => p.exercise_id === ex.id);
      const d = p && perfDay.get(p.id);
      if (d && (!last || d > last)) last = d;
    });
    return last;
  };

  // Suggestion : la catégorie déjà commencée aujourd'hui, sinon la moins récente des catégories
  // travaillées ces 14 derniers jours (Noah alterne Pecs / Dos), sinon la première
  const suggested = (() => {
    const seen = plates.map(p => ({ name: p.name, last: lastDayOf(p.name) }));
    const started = seen.find(p => p.last === today);
    if (started) return started.name;
    const since = shiftDay(today, -14);
    const recent = seen.filter(p => p.last && p.last >= since).sort((a, b) => a.last.localeCompare(b.last));
    if (recent.length) return recent[0].name;
    return plates[0]?.name || null;
  })();

  const [picked, setPicked] = useState(() => {
    try {
      const saved = JSON.parse(localStorage.getItem('seance-category') || 'null');
      return saved && saved.day === getTodayInTimezone(tz) ? saved.name : null;
    } catch (e) {
      return null;
    }
  });
  const selected = plates.some(p => p.name === picked) ? picked : suggested;
  const pick = (name) => {
    setPicked(name);
    try { localStorage.setItem('seance-category', JSON.stringify({ name, day: today })); } catch (e) { /* stockage indisponible */ }
  };

  // Semaine en cours : chaque jour prend la couleur de la catégorie la plus travaillée ce jour-là
  const exById = {};
  exercises.forEach(ex => { exById[ex.id] = ex; });
  const categoryOfExercise = (ex) => {
    const cats = (ex?.categories || []);
    const visible = cats.find(c => !c.hidden && visibleCategories.some(v => v.name === c.name));
    const c = visible || cats[0];
    return c ? { name: c.name, color: c.color } : { name: 'Autre', color: null };
  };
  const dow = (new Date(today + 'T12:00:00Z').getUTCDay() + 6) % 7;
  const monday = shiftDay(today, -dow);
  const byDay = {};
  performances.forEach(p => {
    const d = perfDay.get(p.id);
    if (!d || d < monday) return;
    const c = categoryOfExercise(exById[p.exercise_id]);
    byDay[d] = byDay[d] || {};
    byDay[d][c.name] = byDay[d][c.name] || { ...c, n: 0 };
    byDay[d][c.name].n++;
  });
  const week = WEEK_LETTERS.map((letter, i) => {
    const day = shiftDay(monday, i);
    const top = byDay[day] ? Object.values(byDay[day]).sort((a, b) => b.n - a.n)[0] : null;
    return { day, letter, top };
  });
  const trainedDays = week.filter(w => w.top && w.day <= today).length;
  const previousDay = [...new Set(performances.map(p => perfDay.get(p.id)))].filter(d => d && d < today).sort().pop();
  const previousTop = previousDay
    ? Object.values(performances.filter(p => perfDay.get(p.id) === previousDay).reduce((acc, p) => {
        const c = categoryOfExercise(exById[p.exercise_id]);
        acc[c.name] = acc[c.name] || { ...c, n: 0 };
        acc[c.name].n++;
        return acc;
      }, {})).sort((a, b) => b.n - a.n)[0]
    : null;

  const list = groups[selected] || [];
  const rows = list.map(ex => {
    const { todayPerf, last } = exerciseState(performances, ex.id, today, tz);
    const lastSets = parseSets(last);
    const todaySets = todayPerf ? parseSets(todayPerf) : [];
    const target = ex.unit === 'reps' ? targetOf(lastSets) : null;
    return { ex, last, lastSets, todaySets, target, beaten: beats(todaySets, target) };
  });
  const doneToday = rows.filter(r => r.todaySets.length > 0).length;
  const plateColor = colorOf(selected);

  return (
    <div className="seance pb-safe" style={{ '--cat': plateColor || 'var(--chalk-dim)' }}>
      <div className="seance-top">
        <p className="seance-date">{dayFmt(today, { weekday: 'long', day: 'numeric', month: 'long' })}</p>
        <TopActions onShowStats={onShowStats} onManageCategories={onManageCategories} onShowProfile={onShowProfile} />
      </div>

      <div className="week" aria-label="Ta semaine">
        {week.map(w => (
          <span key={w.day} className={`week-day ${w.day === today ? 'is-today' : ''}`}>
            <i className={`disc ${w.top ? 'is-done' : ''} ${w.day === today && !w.top ? 'is-today' : ''}`} style={w.top ? { '--disc': w.top.color || 'var(--chalk-soft)' } : undefined} title={w.top ? w.top.name : undefined} />
            {w.letter}
          </span>
        ))}
      </div>
      <p className="week-note">
        {trainedDays === 0 ? 'Aucune séance cette semaine pour l’instant.' : `${trainedDays} séance${trainedDays > 1 ? 's' : ''} cette semaine.`}
        {previousTop ? ` La dernière : ${previousTop.name}, ${whenLabel(previousDay, today)}.` : ''}
      </p>

      {plates.length === 0 ? (
        <div className="seance-empty">
          <p>Commence par créer une catégorie (Pecs, Dos…) pour ranger tes exercices.</p>
          <button className="plate-cta" onClick={onManageCategories}>Créer une catégorie</button>
        </div>
      ) : (
        <>
          <div className="plates" role="tablist" aria-label="Séances">
            {plates.map(p => (
              <button key={p.name} role="tab" aria-selected={p.name === selected} className={`plate ${p.name === selected ? 'is-on' : ''}`} onClick={() => pick(p.name)} style={{ '--disc': p.color || 'var(--chalk-soft)' }}>
                <i />{p.name}
              </button>
            ))}
          </div>

          <p className="seance-sub">
            {list.length === 0 ? 'Aucun exercice' : `${list.length} exercice${list.length > 1 ? 's' : ''}`}
            {doneToday > 0 ? `, ${doneToday} commencé${doneToday > 1 ? 's' : ''} aujourd'hui` : ''}
          </p>

          <ul className="ex-list">
            {rows.map(r => (
              <li key={r.ex.id}>
                <button className="ex-row" onClick={() => onOpenExercise(r.ex, selected)}>
                  <i className="ex-bar" />
                  <span className="ex-main">
                    <span className="ex-name">{r.ex.name}</span>
                    {r.ex.unit === 'reps' ? (
                      <span className="ex-last">{r.lastSets.length ? r.lastSets.map(setLabel).join('  ') : 'Pas encore de série'}</span>
                    ) : (
                      <span className="ex-last">{r.last ? formatValue(r.last.value, r.ex.unit, r.last.weight, r.last.series) : 'Pas encore de donnée'}</span>
                    )}
                    {r.todaySets.length > 0 && <span className="ex-today">Aujourd'hui : {r.todaySets.map(setLabel).join('  ')}</span>}
                  </span>
                  {r.target && (
                    <span className={`ex-goal ${r.beaten ? 'is-beaten' : ''}`}>
                      {r.beaten ? <b><Icon name="check" size={22} /></b> : <b>{setLabel(r.target)}</b>}
                      <small>{r.beaten ? 'battu' : 'à battre'}</small>
                    </span>
                  )}
                </button>
              </li>
            ))}
            <li>
              <button className="ex-add" onClick={() => onAddExercise(selected)}><Icon name="plus" size={18} />Ajouter un exercice</button>
            </li>
          </ul>
        </>
      )}

      {hiddenExercises.length > 0 && (
        <section className="hidden-block">
          <button className="hidden-head" onClick={() => setOpenSection(s => ({ ...s, ex: !s.ex }))} aria-expanded={!!openSection.ex}>
            Exercices masqués ({hiddenExercises.length})<Icon name="chevronDown" size={18} />
          </button>
          {openSection.ex && hiddenExercises.map(ex => (
            <div key={ex.id} className="hidden-row">
              <span>
                <span className="hidden-name">{ex.name}</span>
                <span className="hidden-meta">{ex.categories && ex.categories.length > 0 ? ex.categories.map(c => c.name).join(', ') : 'Autre'}</span>
              </span>
              <button className="hidden-show" onClick={() => onToggleHidden(ex.id, false)}>Réafficher</button>
            </div>
          ))}
        </section>
      )}

      {hiddenCategories.length > 0 && (
        <section className="hidden-block">
          <button className="hidden-head" onClick={() => setOpenSection(s => ({ ...s, cat: !s.cat }))} aria-expanded={!!openSection.cat}>
            Catégories masquées ({hiddenCategories.length})<Icon name="chevronDown" size={18} />
          </button>
          {openSection.cat && hiddenCategories.map(cat => (
            <div key={cat.id} className="hidden-row">
              <span className="hidden-name"><i className="hidden-dot" style={{ '--disc': cat.color }} />{cat.name}</span>
              <button className="hidden-show" onClick={() => onToggleCategoryHidden(cat.id, false)}>Réafficher</button>
            </div>
          ))}
        </section>
      )}

    </div>
  );
}

// ============================================
// SAISIE D'UNE SÉRIE (plein écran, unités « reps »)
// ============================================
const parseNum = (str) => {
  const n = parseFloat(String(str).replace(',', '.'));
  return Number.isFinite(n) ? n : null;
};

function SetLogger({ exercise, user, performances, nextExercise, onClose, onNext, onSaveSet, onEditExercise, onToggleHidden, onDeleteExercise, onEditToday }) {
  const tz = user?.timezone || TZ_DEFAULT;
  const today = getTodayInTimezone(tz);
  const { todayPerf, last } = exerciseState(performances, exercise.id, today, tz);
  const lastSets = parseSets(last);
  const todaySets = todayPerf ? parseSets(todayPerf) : [];
  const index = todaySets.length;
  const ref = lastSets[index] || lastSets[lastSets.length - 1] || null;
  const planned = Math.max(lastSets.length, index + 1);
  const allDone = lastSets.length > 0 && index >= lastSets.length;

  const startValues = () => {
    const base = ref || todaySets[index - 1] || { reps: 10, weight: 0 };
    return { weight: kg(base.weight || 0), reps: String(base.reps || 10) };
  };
  const [weight, setWeight] = useState(() => startValues().weight);
  const [reps, setReps] = useState(() => startValues().reps);
  const [menu, setMenu] = useState(false);
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState('');
  const [restEnd, setRestEnd] = useState(null);
  const [now, setNow] = useState(Date.now());

  // Nouvelle série (après validation) : on repart des chiffres de la même série la dernière fois
  useEffect(() => {
    const v = startValues();
    setWeight(v.weight);
    setReps(v.reps);
  }, [index, exercise.id]);

  useEffect(() => {
    if (!restEnd) return;
    const id = setInterval(() => {
      setNow(Date.now());
      if (Date.now() >= restEnd) setRestEnd(null);
    }, 500);
    return () => clearInterval(id);
  }, [restEnd]);

  useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, [onClose]);

  const w = parseNum(weight) ?? 0;
  const r = parseInt(reps, 10) || 0;
  const verdict = r > 0 ? compareSet({ reps: r, weight: w }, ref) : null;
  const stepW = w >= 10 ? 2.5 : 0.5;
  const bump = (delta) => setWeight(kg(Math.max(0, Math.round((w + delta) * 100) / 100)));
  const restLeft = restEnd ? Math.max(0, Math.ceil((restEnd - now) / 1000)) : 0;

  const save = async () => {
    if (r <= 0) { setError('Indique au moins une répétition.'); return; }
    setSaving(true);
    setError('');
    try {
      await onSaveSet(exercise, r, w > 0 ? w : null);
      setRestEnd(Date.now() + REST_SECONDS * 1000);
      setNow(Date.now());
    } catch (err) {
      setError("La série n'a pas été enregistrée. Vérifie ta connexion et réessaie.");
    }
    setSaving(false);
  };

  return (
    <div className="logger-layer" onClick={onClose}>
    <div className="logger" role="dialog" aria-modal="true" aria-label={`Saisie : ${exercise.name}`} onClick={e => e.stopPropagation()}>
      <div className="logger-bar">
        <button className="logger-back" onClick={onClose}>‹ Retour</button>
        <button className="top-btn" onClick={() => setMenu(m => !m)} aria-expanded={menu} aria-label="Options de l'exercice"><Icon name="more" /></button>
      </div>
      {menu && (
        <div className="logger-menu">
          {todayPerf && <button onClick={() => { setMenu(false); onEditToday(todayPerf); }}>Corriger les séries du jour</button>}
          <button onClick={() => { setMenu(false); onEditExercise(exercise); }}>Modifier l'exercice</button>
          <button onClick={() => { setMenu(false); onToggleHidden(exercise.id, true); onClose(); }}>Masquer l'exercice</button>
          <button className="is-danger" onClick={() => {
            if (confirm(`Supprimer l'exercice "${exercise.name}" et toutes ses performances ?`)) {
              setMenu(false);
              onDeleteExercise(exercise.id);
              onClose();
            }
          }}>Supprimer l'exercice</button>
        </div>
      )}

      <h1 className="logger-title">{exercise.name}</h1>
      <p className="logger-sets" aria-label={`Série ${index + 1} sur ${planned}`}>
        {Array.from({ length: planned }, (_, i) => <i key={i} className={i < index ? 'is-done' : i === index ? 'is-now' : ''} />)}
        {allDone ? `${index} séries faites` : `Série ${index + 1}${lastSets.length ? ` sur ${planned}` : ''}`}
      </p>

      {last ? (
        <>
          <p className="last-cap">La dernière fois, {whenLabel(dayOf(last.performed_at, tz), today)}</p>
          <div className={`last-grid ${lastSets.length > 3 ? 'is-dense' : ''}`}>
            {lastSets.map((s, i) => (
              <div key={i} className={i === index ? 'is-now' : ''}>
                <small>Série {i + 1}</small>
                <b>{setLabel(s)}</b>
              </div>
            ))}
          </div>
        </>
      ) : (
        <p className="last-cap">Première fois sur cet exercice : pas encore de référence.</p>
      )}

      <div className="stepper">
        <button className="step-btn" onClick={() => bump(-stepW)} aria-label={`Retirer ${kg(stepW)} kg`}>−</button>
        <label className="step-val">
          <input className="step-input" inputMode="decimal" value={weight} onChange={e => setWeight(e.target.value.replace(/[^0-9.,]/g, ''))} aria-label="Charge en kg" />
          <span>kg</span>
        </label>
        <button className="step-btn" onClick={() => bump(stepW)} aria-label={`Ajouter ${kg(stepW)} kg`}>+</button>
      </div>
      <div className="stepper">
        <button className="step-btn" onClick={() => setReps(String(Math.max(0, r - 1)))} aria-label="Une répétition de moins">−</button>
        <label className="step-val">
          <input className="step-input" inputMode="numeric" value={reps} onChange={e => setReps(e.target.value.replace(/[^0-9]/g, ''))} aria-label="Répétitions" />
          <span>répétitions</span>
        </label>
        <button className="step-btn" onClick={() => setReps(String(r + 1))} aria-label="Une répétition de plus">+</button>
      </div>

      {verdict && (
        <p className={`verdict is-${verdict.tone}`}>
          {verdict.tone === 'up' && <Icon name="check" />}
          {verdict.text}
        </p>
      )}
      {todaySets.length > 0 && (
        <p className="done-line">Aujourd'hui : {todaySets.map((s, i) => <b key={i}>{setLabel(s)}</b>)}</p>
      )}
      {error && <p className="logger-error">{error}</p>}

      <div className="logger-foot">
        {restLeft > 0 ? (
          <div className="rest">
            <span>Repos <b>{formatClock(restLeft)}</b></span>
            <button className="rest-skip" onClick={() => setRestEnd(null)}>Passer</button>
          </div>
        ) : allDone && nextExercise ? (
          <button className="logger-cta is-next" onClick={onNext}>Exercice suivant : {nextExercise.name}</button>
        ) : null}
        <button className={`logger-cta ${restLeft > 0 || (allDone && nextExercise) ? 'is-quiet' : ''}`} onClick={save} disabled={saving}>
          {saving ? 'Enregistrement…' : `Valider la série ${index + 1}`}
        </button>
        <p className="rest-note">{restLeft > 0 ? 'Ta série est notée.' : `Le repos de ${REST_SECONDS / 60} minutes démarre à la validation.`}</p>
      </div>
    </div>
    </div>
  );
}

// Add/Edit Performance Modal
function AddPerformanceModal({ exercise, editingPerformance, onClose, onSave, onDeleteExercise, onToggleHidden, onDeletePerformance, onEditExercise, performances }) {
  // Pour temps, convertir secondes en h:min:sec
  const getInitialValue = () => {
    if (editingPerformance) {
      return editingPerformance.value;
    }
    // Obtenir la dernière valeur saisie (non nulle)
    const perfs = performances.filter(p => p.exercise_id === exercise.id && p.value > 0);
    if (perfs.length > 0) {
      const lastPerf = perfs[0];
      // Si la dernière perf a des séries, prendre les reps de la dernière série
      if (lastPerf.series) {
        try {
          const seriesArray = typeof lastPerf.series === 'string' ? JSON.parse(lastPerf.series) : lastPerf.series;
          if (seriesArray && seriesArray.length > 0) {
            const lastSeries = seriesArray[seriesArray.length - 1];
            return lastSeries.reps || '';
          }
        } catch (e) {
          // En cas d'erreur, utiliser la valeur simple
        }
      }
      return lastPerf.value;
    }
    return '';
  };

  const getInitialWeight = () => {
    if (editingPerformance && editingPerformance.weight) {
      return editingPerformance.weight;
    }
    // Obtenir la dernière charge saisie (non nulle)
    const perfs = performances.filter(p => p.exercise_id === exercise.id && p.weight > 0);
    if (perfs.length > 0) {
      const lastPerf = perfs[0];
      // Si la dernière perf a des séries, prendre le poids de la dernière série
      if (lastPerf.series) {
        try {
          const seriesArray = typeof lastPerf.series === 'string' ? JSON.parse(lastPerf.series) : lastPerf.series;
          if (seriesArray && seriesArray.length > 0) {
            const lastSeries = seriesArray[seriesArray.length - 1];
            return lastSeries.weight || '';
          }
        } catch (e) {
          // En cas d'erreur, utiliser le poids simple
        }
      }
      return lastPerf.weight;
    }
    return '';
  };

  const getInitialSeries = () => {
    if (editingPerformance && editingPerformance.series) {
      try {
        return JSON.parse(editingPerformance.series);
      } catch (e) {
        return [];
      }
    }
    return [];
  };

  const [value, setValue] = useState(getInitialValue());
  const [hours, setHours] = useState(editingPerformance && exercise.unit === 'temps' ? Math.floor(editingPerformance.value / 3600) : '');
  const [minutes, setMinutes] = useState(editingPerformance && exercise.unit === 'temps' ? Math.floor((editingPerformance.value % 3600) / 60) : '');
  const [seconds, setSeconds] = useState(editingPerformance && exercise.unit === 'temps' ? Math.floor(editingPerformance.value % 60) : '');
  const [weight, setWeight] = useState(getInitialWeight());
  const [notes, setNotes] = useState(editingPerformance?.notes || '');

  // Series state (only for editing existing performances with series)
  const [series, setSeries] = useState(getInitialSeries());

  const handleSubmit = (e) => {
    e.preventDefault();

    // Si modification d'une performance avec séries, conserver les séries
    if (editingPerformance && series.length > 0) {
      const seriesData = series.map(s => ({
        reps: parseFloat(s.reps) || 0,
        weight: s.weight ? parseFloat(s.weight) : null
      }));
      onSave(null, null, notes, seriesData);
    } else {
      // Mode normal
      let finalValue = value;

      // Si unité = temps, convertir h:min:sec en secondes totales
      if (exercise.unit === 'temps') {
        const h = parseInt(hours) || 0;
        const m = parseInt(minutes) || 0;
        const s = parseInt(seconds) || 0;
        finalValue = (h * 3600) + (m * 60) + s;
      }

      onSave(parseFloat(finalValue), weight ? parseFloat(weight) : null, notes, null);
    }
  };

  // Fonctions pour gérer les séries (modification uniquement)
  const updateSeriesReps = (index, value) => {
    const newSeries = [...series];
    newSeries[index].reps = value;
    setSeries(newSeries);
  };

  const updateSeriesWeight = (index, value) => {
    const newSeries = [...series];
    newSeries[index].weight = value;
    setSeries(newSeries);
  };

  const increment = () => {
    if (exercise.unit === 'temps') {
      const h = parseInt(hours) || 0;
      const m = parseInt(minutes) || 0;
      const s = parseInt(seconds) || 0;

      const newSeconds = s + 1;
      if (newSeconds >= 60) {
        setSeconds(0);
        const newMinutes = m + 1;
        if (newMinutes >= 60) {
          setMinutes(0);
          setHours(h + 1);
        } else {
          setMinutes(newMinutes);
        }
      } else {
        setSeconds(newSeconds);
      }
    } else {
      setValue(value ? parseFloat(value) + 1 : 1);
    }
  };

  const decrement = () => {
    if (exercise.unit === 'temps') {
      const h = parseInt(hours) || 0;
      const m = parseInt(minutes) || 0;
      const s = parseInt(seconds) || 0;

      if (h === 0 && m === 0 && s === 0) return;
      const newSeconds = s - 1;
      if (newSeconds < 0) {
        setSeconds(59);
        const newMinutes = m - 1;
        if (newMinutes < 0) {
          setMinutes(59);
          setHours(Math.max(0, h - 1));
        } else {
          setMinutes(newMinutes);
        }
      } else {
        setSeconds(newSeconds);
      }
    } else {
      setValue(Math.max(0, value ? parseFloat(value) - 1 : 0));
    }
  };

  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal fade-in" onClick={(e) => e.stopPropagation()}>
        <div className="modal-header" style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%' }}>
            <span>{editingPerformance ? 'Modifier' : 'Ajouter'} - {exercise.name}</span>
            {editingPerformance ? (
              <button
                type="button"
                className="action-btn delete"
                onClick={() => {
                  if (confirm('Supprimer cette performance ?')) {
                    onDeletePerformance(editingPerformance.id);
                    onClose();
                  }
                }}
                title="Supprimer la performance"
                style={{ position: 'relative', top: 0, right: 0 }}
              >
                <Icon name="trash" size={18} />
              </button>
            ) : (
              <div style={{ display: 'flex', gap: '6px', alignItems: 'center' }}>
                <button
                  type="button"
                  className="action-btn"
                  onClick={() => {
                    onToggleHidden(exercise.id, true);
                    onClose();
                  }}
                  title="Masquer l'exercice"
                  style={{ position: 'relative', top: 0, right: 0 }}
                >
                  <Icon name="eyeOff" size={18} />
                </button>
                <button
                  type="button"
                  className="action-btn delete"
                  onClick={() => {
                    if (confirm(`Supprimer l'exercice "${exercise.name}" et toutes ses performances ?`)) {
                      onDeleteExercise(exercise.id);
                      onClose();
                    }
                  }}
                  title="Supprimer l'exercice"
                  style={{ position: 'relative', top: 0, right: 0 }}
                >
                  <Icon name="trash" size={18} />
                </button>
              </div>
            )}
          </div>
          {exercise.categories && exercise.categories.length > 0 && (
            <div style={{
              display: 'flex',
              gap: '6px',
              justifyContent: 'center',
              alignItems: 'center',
              flexWrap: 'wrap'
            }}>
              {exercise.categories.map(cat => (
                <span
                  key={cat.id}
                  style={{
                    fontSize: '11px',
                    fontWeight: '600',
                    padding: '4px 10px',
                    borderRadius: '12px',
                    backgroundColor: cat.color,
                    color: 'white',
                    whiteSpace: 'nowrap'
                  }}
                >
                  {cat.name}
                </span>
              ))}
            </div>
          )}
        </div>

        <form onSubmit={handleSubmit}>
          {/* Affichage des séries (en lecture seule si modification d'une performance avec séries) */}
          {editingPerformance && series.length > 0 ? (
            <div className="form-group">
              <label className="form-label">Séries enregistrées ({series.length})</label>
              <div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
                {series.map((s, index) => (
                  <div key={index} className="series-row" style={{ display: 'flex', gap: '10px', alignItems: 'stretch', padding: '10px 12px', backgroundColor: 'var(--floor)', borderRadius: '8px' }}>
                    <span style={{ fontWeight: '600', fontSize: '13px', minWidth: '60px', display: 'flex', alignItems: 'center', color: 'var(--chalk)' }}>
                      Série {index + 1}
                    </span>
                    <div style={{ flex: 1, display: 'flex', gap: '10px', alignItems: 'center' }}>
                      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }}>
                        <input
                          type="number"
                          step="any"
                          className="form-input"
                          value={s.reps}
                          onChange={(e) => updateSeriesReps(index, e.target.value)}
                          placeholder="0"
                          required
                          style={{ textAlign: 'center', fontSize: '16px', fontWeight: '500', width: '100%' }}
                        />
                        <label style={{ fontSize: '10px', textAlign: 'center', color: 'var(--chalk-soft)', marginTop: '4px', fontWeight: '500' }}>Reps</label>
                      </div>
                      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }}>
                        <input
                          type="number"
                          step="0.5"
                          className="form-input"
                          value={s.weight}
                          onChange={(e) => updateSeriesWeight(index, e.target.value)}
                          placeholder="0"
                          style={{ textAlign: 'center', fontSize: '16px', fontWeight: '500', width: '100%' }}
                        />
                        <label style={{ fontSize: '10px', textAlign: 'center', color: 'var(--chalk-soft)', marginTop: '4px', fontWeight: '500' }}>Poids (kg)</label>
                      </div>
                    </div>
                  </div>
                ))}
              </div>
            </div>
          ) : (
            <div className="form-group">
              <label className="form-label">
                Valeur ({exercise.unit})
              </label>
            {exercise.unit === 'temps' ? (
              <div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
                <div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
                  <label className="form-label" style={{ fontSize: '12px', marginBottom: '4px' }}>H</label>
                  <input
                    type="number"
                    min="0"
                    className="form-input"
                    value={hours}
                    onChange={(e) => setHours(e.target.value === '' ? '' : parseInt(e.target.value) || '')}
                    placeholder="0"
                    style={{ textAlign: 'center' }}
                  />
                </div>
                <span style={{ fontSize: '20px', fontWeight: 'bold' }}>:</span>
                <div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
                  <label className="form-label" style={{ fontSize: '12px', marginBottom: '4px' }}>Min</label>
                  <input
                    type="number"
                    min="0"
                    max="59"
                    className="form-input"
                    value={minutes}
                    onChange={(e) => {
                      const val = e.target.value === '' ? '' : parseInt(e.target.value);
                      setMinutes(val === '' ? '' : Math.min(59, val || ''));
                    }}
                    placeholder="0"
                    style={{ textAlign: 'center' }}
                  />
                </div>
                <span style={{ fontSize: '20px', fontWeight: 'bold' }}>:</span>
                <div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
                  <label className="form-label" style={{ fontSize: '12px', marginBottom: '4px' }}>Sec</label>
                  <input
                    type="number"
                    min="0"
                    max="59"
                    className="form-input"
                    value={seconds}
                    onChange={(e) => {
                      const val = e.target.value === '' ? '' : parseInt(e.target.value);
                      setSeconds(val === '' ? '' : Math.min(59, val || ''));
                    }}
                    placeholder="0"
                    style={{ textAlign: 'center' }}
                  />
                </div>
              </div>
            ) : (
              <div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
                <button type="button" className="btn btn-secondary" onClick={decrement} style={{ width: '48px', height: '48px', fontSize: '24px', padding: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                  −
                </button>
                <input
                  type="number"
                  step="any"
                  className="form-input"
                  value={value}
                  onChange={(e) => setValue(e.target.value)}
                  placeholder="12"
                  required
                  style={{ flex: 1, textAlign: 'center', fontSize: '20px', fontWeight: 'bold' }}
                />
                <button type="button" className="btn btn-secondary" onClick={increment} style={{ width: '48px', height: '48px', fontSize: '24px', padding: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                  +
                </button>
              </div>
            )}
            </div>
          )}

          {!(editingPerformance && series.length > 0) && ['reps', 'temps'].includes(exercise.unit) && (
            <div className="form-group">
              <label className="form-label">
                Poids (kg) - optionnel
              </label>
              <div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
                <button
                  type="button"
                  className="btn btn-secondary"
                  onClick={() => setWeight(Math.max(0, weight ? parseFloat(weight) - 0.5 : 0))}
                  style={{ width: '48px', height: '48px', fontSize: '24px', padding: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' }}
                >
                  −
                </button>
                <input
                  type="number"
                  step="0.5"
                  className="form-input"
                  value={weight}
                  onChange={(e) => setWeight(e.target.value)}
                  placeholder="75"
                  style={{ flex: 1, textAlign: 'center', fontSize: '20px', fontWeight: 'bold' }}
                />
                <button
                  type="button"
                  className="btn btn-secondary"
                  onClick={() => setWeight(weight ? parseFloat(weight) + 0.5 : 0.5)}
                  style={{ width: '48px', height: '48px', fontSize: '24px', padding: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' }}
                >
                  +
                </button>
              </div>
            </div>
          )}

          <div className="form-group">
            <label className="form-label">Notes - optionnel</label>
            <input
              type="text"
              className="form-input"
              value={notes}
              onChange={(e) => setNotes(e.target.value)}
              placeholder="Commentaire..."
            />
          </div>

          <div className="form-actions" style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
            <button type="button" className="btn btn-secondary" onClick={onClose}>
              Annuler
            </button>
            <button
              type="button"
              className="btn btn-secondary"
              onClick={() => {
                onClose();
                onEditExercise(exercise);
              }}
              style={{ flex: '1 1 auto' }}
            >
              Modifier l'exercice
            </button>
            <button type="submit" className="btn btn-primary" style={{ flex: '1 1 auto' }}>
              {editingPerformance ? 'Modifier' : 'Enregistrer'}
            </button>
          </div>
        </form>
      </div>
    </div>
  );
}

// Add/Edit Exercise Modal
function AddExerciseModal({ exercise, categories, defaultCategoryId, onClose, onSave }) {
  const [name, setName] = useState(exercise?.name || '');
  const [unit, setUnit] = useState(exercise?.unit || 'reps');
  const [selectedCategories, setSelectedCategories] = useState(() => {
    if (exercise?.categories) {
      return exercise.categories.map(c => c.id);
    }
    if (defaultCategoryId && categories.some(c => c.id === defaultCategoryId)) return [defaultCategoryId];
    return categories.length > 0 ? [categories[0].id] : [];
  });

  const toggleCategory = (categoryId) => {
    setSelectedCategories(prev => {
      if (prev.includes(categoryId)) {
        // Empêcher de décocher si c'est la dernière catégorie
        if (prev.length === 1) {
          alert('Un exercice doit appartenir à au moins une catégorie');
          return prev;
        }
        return prev.filter(id => id !== categoryId);
      } else {
        return [...prev, categoryId];
      }
    });
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    if (!name || !unit || selectedCategories.length === 0) {
      alert('Nom, unité et au moins une catégorie requis');
      return;
    }

    onSave({
      name,
      unit,
      categories: selectedCategories
    });
  };

  const units = ['reps', 'temps', 'km'];

  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal fade-in" onClick={(e) => e.stopPropagation()}>
        <div className="modal-header">
          {exercise ? 'Modifier' : 'Nouvel'} exercice
        </div>

        <form onSubmit={handleSubmit}>
          <div className="form-group">
            <label className="form-label">Nom</label>
            <input
              type="text"
              className="form-input"
              value={name}
              onChange={(e) => setName(e.target.value)}
              placeholder="Squat"
              required
              autoFocus
            />
          </div>

          <div className="form-group">
            <label className="form-label">Unité</label>
            <select
              className="form-input"
              value={unit}
              onChange={(e) => setUnit(e.target.value)}
            >
              {units.map(u => (
                <option key={u} value={u}>{u}</option>
              ))}
            </select>
          </div>

          <div className="form-group">
            <label className="form-label">Catégories</label>
            <div style={{
              display: 'flex',
              flexDirection: 'column',
              gap: '8px',
              maxHeight: '200px',
              overflowY: 'auto',
              padding: '8px',
              border: '1px solid var(--border-color, #e5e7eb)',
              borderRadius: '8px'
            }}>
              {categories.length === 0 ? (
                <div style={{ color: 'var(--chalk-dim)', fontSize: '14px', padding: '8px' }}>
                  Aucune catégorie. Créez-en une d'abord.
                </div>
              ) : (
                categories.map(cat => (
                  <label
                    key={cat.id}
                    style={{
                      display: 'flex',
                      alignItems: 'center',
                      gap: '8px',
                      padding: '8px',
                      borderRadius: '6px',
                      cursor: 'pointer',
                      background: selectedCategories.includes(cat.id)
                        ? 'var(--floor-3)'
                        : 'transparent'
                    }}
                  >
                    <input
                      type="checkbox"
                      checked={selectedCategories.includes(cat.id)}
                      onChange={() => toggleCategory(cat.id)}
                      style={{ width: '18px', height: '18px', cursor: 'pointer' }}
                    />
                    <div
                      style={{
                        width: '12px',
                        height: '12px',
                        borderRadius: '50%',
                        background: cat.color,
                        flexShrink: 0
                      }}
                    />
                    <span>{cat.name}</span>
                  </label>
                ))
              )}
            </div>
            <div style={{ fontSize: '12px', color: 'var(--chalk-dim)', marginTop: '4px' }}>
              {selectedCategories.length} catégorie(s) sélectionnée(s)
            </div>
          </div>

          <div className="form-actions">
            <button type="button" className="btn btn-secondary" onClick={onClose}>
              Annuler
            </button>
            <button type="submit" className="btn btn-primary">
              {exercise ? 'Modifier' : 'Créer'}
            </button>
          </div>
        </form>
      </div>
    </div>
  );
}

// Graphiques d'avant la refonte (courbe par exercice, comparaison, historique), ouverts depuis Progrès
function ProgressClassic({ user, exercises, performances, onEditPerformance, onDeletePerformance, onShowStats, onManageCategories, onShowProfile, onBack }) {
  const [selectedExercise, setSelectedExercise] = useState(() => {
    const savedExerciseId = localStorage.getItem('progress-view-selected-exercise');
    return savedExerciseId ? { id: parseInt(savedExerciseId) } : null;
  });
  const [filter, setFilter] = useState(() => {
    const savedFilter = localStorage.getItem('progress-view-filter');
    return savedFilter || 'all';
  });
  const [comparisonMode, setComparisonMode] = useState(() => {
    const savedMode = localStorage.getItem('progress-view-comparison-mode');
    return savedMode === 'true';
  });
  const [selectedExercises, setSelectedExercises] = useState(() => {
    const savedExercises = localStorage.getItem('progress-view-selected-exercises');
    return savedExercises ? JSON.parse(savedExercises) : [];
  });
  const [expandedCategories, setExpandedCategories] = useState(() => {
    const savedExpanded = localStorage.getItem('progress-view-expanded-categories');
    return savedExpanded ? JSON.parse(savedExpanded) : {};
  });
  const [historyLimit, setHistoryLimit] = useState(10);
  const chartRef = useRef(null);
  const chartInstance = useRef(null);

  // Sauvegarder les états dans localStorage
  useEffect(() => {
    if (selectedExercise) {
      localStorage.setItem('progress-view-selected-exercise', selectedExercise.id.toString());
    }
  }, [selectedExercise]);

  useEffect(() => {
    localStorage.setItem('progress-view-filter', filter);
  }, [filter]);

  useEffect(() => {
    localStorage.setItem('progress-view-comparison-mode', comparisonMode.toString());
  }, [comparisonMode]);

  useEffect(() => {
    localStorage.setItem('progress-view-selected-exercises', JSON.stringify(selectedExercises));
  }, [selectedExercises]);

  useEffect(() => {
    localStorage.setItem('progress-view-expanded-categories', JSON.stringify(expandedCategories));
  }, [expandedCategories]);

  useEffect(() => {
    if (exercises.length > 0) {
      // Si on a un exercice sauvegardé, le restaurer
      const savedExerciseId = localStorage.getItem('progress-view-selected-exercise');
      if (savedExerciseId) {
        const exercise = exercises.find(ex => ex.id === parseInt(savedExerciseId));
        if (exercise) {
          setSelectedExercise(exercise);
        } else if (!selectedExercise) {
          setSelectedExercise(exercises[0]);
        }
      } else if (!selectedExercise) {
        setSelectedExercise(exercises[0]);
      }
    }
  }, [exercises]);

  const toggleExerciseSelection = (exerciseId) => {
    setSelectedExercises(prev => {
      if (prev.includes(exerciseId)) {
        return prev.filter(id => id !== exerciseId);
      } else {
        return [...prev, exerciseId];
      }
    });
  };

  const toggleCategoryExpansion = (category) => {
    setExpandedCategories(prev => ({
      ...prev,
      [category]: !prev[category]
    }));
  };

  const selectAllInCategory = (category) => {
    const categoryExercises = exercises.filter(ex => ex.category === category);
    const allSelected = categoryExercises.every(ex => selectedExercises.includes(ex.id));

    if (allSelected) {
      setSelectedExercises(prev => prev.filter(id => !categoryExercises.find(ex => ex.id === id)));
    } else {
      setSelectedExercises(prev => [...new Set([...prev, ...categoryExercises.map(ex => ex.id)])]);
    }
  };

  // Grouper les exercices par catégorie
  const groupedExercises = exercises.reduce((acc, exercise) => {
    const category = exercise.category || 'Autre';
    if (!acc[category]) acc[category] = [];
    acc[category].push(exercise);
    return acc;
  }, {});

  useEffect(() => {
    if (chartRef.current) {
      const ctx = chartRef.current.getContext('2d');

      if (chartInstance.current) {
        chartInstance.current.destroy();
      }

      const now = new Date();
      const colors = ['#3A78E0', '#F2C230', '#ef4444', '#10b981', '#f59e0b', '#8b5cf6', '#ec4899', '#14b8a6'];

      if (comparisonMode && selectedExercises.length > 0) {
        // Mode comparaison multi-exercices
        const datasets = selectedExercises.map((exId, index) => {
          const exercise = exercises.find(ex => ex.id === exId);
          if (!exercise) return null;

          let perfs = performances.filter(p => p.exercise_id === exId);

          // Apply filter
          if (filter === 'week') {
            perfs = perfs.filter(p => new Date(p.performed_at) >= new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000));
          } else if (filter === 'month') {
            perfs = perfs.filter(p => new Date(p.performed_at) >= new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000));
          } else if (filter === 'year') {
            perfs = perfs.filter(p => new Date(p.performed_at) >= new Date(now.getTime() - 365 * 24 * 60 * 60 * 1000));
          }

          const data = perfs.reverse().slice(-20);
          const color = colors[index % colors.length];

          return {
            label: exercise.name,
            data: data.map(p => ({
              x: formatDate(p.performed_at, 'short', user?.timezone || 'Europe/Paris'),
              y: calculatePerformanceVolume(p),
              weight: p.weight,
              reps: p.value,
              series: p.series
            })),
            borderColor: color,
            backgroundColor: color.replace(')', ', 0.1)').replace('rgb', 'rgba'),
            tension: 0.4,
            fill: false,
            pointRadius: 6,
            pointHoverRadius: 10,
            pointHitRadius: 15
          };
        }).filter(Boolean);

        // Créer un ensemble unique de toutes les dates
        const allDates = new Set();
        datasets.forEach(dataset => {
          dataset.data.forEach(point => allDates.add(point.x));
        });
        const sortedDates = Array.from(allDates).sort();

        chartInstance.current = new Chart(ctx, {
          type: 'line',
          data: {
            labels: sortedDates,
            datasets: datasets.map(ds => ({
              ...ds,
              data: sortedDates.map(date => {
                const point = ds.data.find(p => p.x === date);
                return point ? point.y : null;
              }),
              perfDetails: sortedDates.map(date => {
                const point = ds.data.find(p => p.x === date);
                return point || null;
              }),
              spanGaps: true,
              pointRadius: 6,
              pointHoverRadius: 10,
              pointHitRadius: 15
            }))
          },
          options: {
            responsive: true,
            maintainAspectRatio: false,
            interaction: {
              mode: 'nearest',
              intersect: false,
              axis: 'x'
            },
            plugins: {
              legend: {
                display: true,
                position: 'top',
                labels: {
                  boxWidth: 12,
                  padding: 8,
                  font: { size: 11 }
                }
              },
              tooltip: {
                enabled: true,
                mode: 'nearest',
                intersect: false,
                backgroundColor: 'rgba(0, 0, 0, 0.8)',
                padding: 12,
                displayColors: true,
                callbacks: {
                  title: function(context) {
                    return (context && context[0] && context[0].label) || '';
                  },
                  label: function(context) {
                    if (!context.dataset.perfDetails) return context.dataset.label + ': -';

                    const details = context.dataset.perfDetails[context.dataIndex];
                    if (!details) return context.dataset.label + ': -';

                    const lines = [context.dataset.label + ':'];

                    // Si séries multiples, afficher détails de chaque série
                    if (details.series) {
                      try {
                        const seriesArray = typeof details.series === 'string' ? JSON.parse(details.series) : details.series;
                        if (seriesArray && seriesArray.length > 0) {
                          lines.push('  Volume total: ' + context.parsed.y);
                          seriesArray.forEach((s, idx) => {
                            const reps = s.reps || 0;
                            const weight = s.weight ? ` × ${s.weight}kg` : '';
                            lines.push(`  Série ${idx + 1}: ${reps}${weight}`);
                          });
                          return lines;
                        }
                      } catch (e) {
                        // Continuer avec format simple en cas d'erreur
                      }
                    }

                    // Format simple
                    if (details.weight) {
                      lines.push('  Volume total: ' + context.parsed.y);
                      lines.push('  Charge: ' + details.weight + ' kg');
                      lines.push('  Répétitions: ' + details.reps);
                    } else {
                      lines.push('  Répétitions: ' + context.parsed.y);
                    }

                    return lines;
                  }
                }
              }
            },
            scales: {
              y: {
                beginAtZero: true,
                grid: { color: 'rgba(0,0,0,0.05)' },
                ticks: { color: '#5F6368' }
              },
              x: {
                grid: { color: 'rgba(0,0,0,0.03)' },
                ticks: { color: '#5F6368', maxRotation: 45, minRotation: 45 }
              }
            }
          }
        });
      } else if (selectedExercise) {
        // Mode single exercice
        let perfs = performances.filter(p => p.exercise_id === selectedExercise.id);

        // Apply filter
        if (filter === 'week') {
          perfs = perfs.filter(p => new Date(p.performed_at) >= new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000));
        } else if (filter === 'month') {
          perfs = perfs.filter(p => new Date(p.performed_at) >= new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000));
        } else if (filter === 'year') {
          perfs = perfs.filter(p => new Date(p.performed_at) >= new Date(now.getTime() - 365 * 24 * 60 * 60 * 1000));
        }

        const data = perfs.reverse().slice(-20);
        const hasWeight = data.some(p => p.weight);

        chartInstance.current = new Chart(ctx, {
          type: 'line',
          data: {
            labels: data.map(p => formatDate(p.performed_at, 'short', user?.timezone || 'Europe/Paris')),
            datasets: [{
              label: selectedExercise.name + (hasWeight ? ' (Volume total)' : ' (Répétitions)'),
              data: data.map(p => calculatePerformanceVolume(p)),
              borderColor: '#3A78E0',
              backgroundColor: 'rgba(58,120,224,0.12)',
              tension: 0.4,
              fill: true,
              pointRadius: 6,
              pointHoverRadius: 10,
              pointHitRadius: 15,
              perfData: data
            }]
          },
          options: {
            responsive: true,
            maintainAspectRatio: false,
            interaction: {
              mode: 'nearest',
              intersect: false,
              axis: 'x'
            },
            plugins: {
              legend: { display: false },
              tooltip: {
                enabled: true,
                mode: 'nearest',
                intersect: false,
                backgroundColor: 'rgba(0, 0, 0, 0.8)',
                padding: 12,
                displayColors: true,
                callbacks: {
                  title: function(context) {
                    return (context && context[0] && context[0].label) || '';
                  },
                  label: function(context) {
                    if (!context.dataset.perfData) return context.dataset.label + ': ' + context.parsed.y;

                    const perf = context.dataset.perfData[context.dataIndex];
                    if (!perf) return context.dataset.label + ': ' + context.parsed.y;

                    const lines = [];

                    // Si séries multiples, afficher détails de chaque série
                    if (perf.series) {
                      try {
                        const seriesArray = typeof perf.series === 'string' ? JSON.parse(perf.series) : perf.series;
                        if (seriesArray && seriesArray.length > 0) {
                          lines.push('Volume total: ' + context.parsed.y);
                          seriesArray.forEach((s, idx) => {
                            const reps = s.reps || 0;
                            const weight = s.weight ? ` × ${s.weight}kg` : '';
                            lines.push(`Série ${idx + 1}: ${reps}${weight}`);
                          });
                          return lines;
                        }
                      } catch (e) {
                        // Continuer avec format simple en cas d'erreur
                      }
                    }

                    // Format simple
                    if (perf.weight) {
                      lines.push('Volume total: ' + context.parsed.y);
                      lines.push('Charge: ' + perf.weight + ' kg');
                      lines.push('Répétitions: ' + perf.value);
                    } else {
                      lines.push('Répétitions: ' + context.parsed.y);
                    }

                    return lines;
                  }
                }
              }
            },
            scales: {
              y: {
                beginAtZero: true,
                grid: { color: 'rgba(0,0,0,0.05)' },
                ticks: { color: '#5F6368' }
              },
              x: {
                grid: { color: 'rgba(0,0,0,0.03)' },
                ticks: { color: '#5F6368' }
              }
            }
          }
        });
      }
    }

    return () => {
      if (chartInstance.current) {
        chartInstance.current.destroy();
      }
    };
  }, [selectedExercise, performances, filter, comparisonMode, selectedExercises, exercises]);

  const filteredPerfs = performances.filter(p => p.exercise_id === selectedExercise?.id);
  const avgValue = filteredPerfs.length > 0 
    ? filteredPerfs.reduce((sum, p) => sum + p.value, 0) / filteredPerfs.length 
    : 0;
  const maxValue = filteredPerfs.length > 0 
    ? Math.max(...filteredPerfs.map(p => p.value)) 
    : 0;

  return (
    <div className="app-container pb-safe view-transition">
      <div className="header">
        <button className="back-link is-inline" onClick={onBack}>‹ Progrès</button>
        <h1>Graphiques</h1>
        <div className="header-actions">
          <button
            className={`btn-icon ${comparisonMode ? 'active' : ''}`}
            onClick={() => {
              setComparisonMode(!comparisonMode);
              if (!comparisonMode) {
                setSelectedExercises([]);
              }
            }}
            title="Mode comparaison"
            style={{ background: comparisonMode ? '#3b82f6' : 'transparent', color: comparisonMode ? 'white' : 'inherit' }}
          >
            📊
          </button>
          <button className="btn-icon" onClick={onShowStats} title="Statistiques">
            📊
          </button>
          <button className="btn-icon" onClick={onManageCategories} title="Gérer les catégories">
            🏷️
          </button>
          <button className="btn-icon" onClick={onShowProfile} title="Profil">
            ⚙️
          </button>
        </div>
      </div>

      {comparisonMode ? (
        <>
          <div className="comparison-panel">
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px' }}>
              <div className="comparison-label">
                Sélectionnez les exercices à comparer:
              </div>
              <button
                className="btn btn-secondary"
                onClick={() => {
                  if (selectedExercises.length === exercises.length) {
                    setSelectedExercises([]);
                  } else {
                    setSelectedExercises(exercises.map(ex => ex.id));
                  }
                }}
                style={{ padding: '6px 12px', fontSize: '12px' }}
              >
                {selectedExercises.length === exercises.length ? 'Tout désélectionner' : 'Tout sélectionner'}
              </button>
            </div>
            <div style={{ maxHeight: '300px', overflowY: 'auto' }}>
              {Object.keys(groupedExercises).map(category => {
                const categoryExercises = groupedExercises[category];
                const isExpanded = expandedCategories[category] !== false;
                const allSelected = categoryExercises.every(ex => selectedExercises.includes(ex.id));
                const someSelected = categoryExercises.some(ex => selectedExercises.includes(ex.id)) && !allSelected;

                return (
                  <div key={category} style={{ marginBottom: '12px' }}>
                    <div
                      className="comparison-category-header"
                      onClick={() => toggleCategoryExpansion(category)}
                    >
                      <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
                        <span className="comparison-category-name">{category}</span>
                        <span className="comparison-category-count">
                          ({categoryExercises.filter(ex => selectedExercises.includes(ex.id)).length}/{categoryExercises.length})
                        </span>
                      </div>
                      <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
                        <button
                          className="btn-icon"
                          onClick={(e) => {
                            e.stopPropagation();
                            selectAllInCategory(category);
                          }}
                          style={{
                            width: '24px',
                            height: '24px',
                            fontSize: '12px',
                            background: allSelected ? '#1A1A1A' : someSelected ? '#5F6368' : '#E8EAED',
                            color: allSelected || someSelected ? 'white' : '#5F6368'
                          }}
                          title={allSelected ? 'Tout désélectionner' : 'Tout sélectionner'}
                        >
                          ✓
                        </button>
                        <span style={{ fontSize: '16px', color: '#80868B', transition: 'transform 0.2s', transform: isExpanded ? 'rotate(90deg)' : 'rotate(0deg)' }}>
                          ›
                        </span>
                      </div>
                    </div>

                    {isExpanded && categoryExercises.map(ex => (
                      <label
                        key={ex.id}
                        className={`exercise-selection-item ${selectedExercises.includes(ex.id) ? 'selected' : ''}`}
                      >
                        <input
                          type="checkbox"
                          checked={selectedExercises.includes(ex.id)}
                          onChange={() => toggleExerciseSelection(ex.id)}
                          style={{ marginRight: '12px', width: '16px', height: '16px', cursor: 'pointer' }}
                        />
                        <span className="exercise-selection-name">{ex.name}</span>
                      </label>
                    ))}
                  </div>
                );
              })}
            </div>

            <div className="category-filters" style={{ padding: 0, background: 'transparent', border: 'none', marginTop: '16px' }}>
              {['all', 'week', 'month', 'year'].map(f => (
                <button
                  key={f}
                  className={`category-filter ${filter === f ? 'active' : ''}`}
                  onClick={() => setFilter(f)}
                >
                  {f === 'all' ? 'Tout' : f === 'week' ? '7j' : f === 'month' ? '30j' : '365j'}
                </button>
              ))}
            </div>
          </div>

          {selectedExercises.length > 0 && (
            <div className="chart-container">
              <div className="chart-title">Comparaison</div>
              <div style={{ height: '300px' }}>
                <canvas ref={chartRef}></canvas>
              </div>
            </div>
          )}

          {selectedExercises.length === 0 && (
            <div className="comparison-empty">
              Sélectionnez au moins un exercice pour voir le graphique
            </div>
          )}
        </>
      ) : selectedExercise && (
        <>
          <div className="progress-filter-section">
            <div className="form-group" style={{ marginBottom: '16px' }}>
              <select
                className="form-input"
                value={selectedExercise.id}
                onChange={(e) => setSelectedExercise(exercises.find(ex => ex.id === parseInt(e.target.value)))}
              >
                {exercises.map(ex => (
                  <option key={ex.id} value={ex.id}>{ex.name}</option>
                ))}
              </select>
            </div>

            <div className="category-filters" style={{ padding: 0, background: 'transparent', border: 'none' }}>
              {['all', 'week', 'month', 'year'].map(f => (
                <button
                  key={f}
                  className={`category-filter ${filter === f ? 'active' : ''}`}
                  onClick={() => setFilter(f)}
                >
                  {f === 'all' ? 'Tout' : f === 'week' ? '7j' : f === 'month' ? '30j' : '365j'}
                </button>
              ))}
            </div>
          </div>

          <div className="stats-grid">
            <div className="stat-card">
              <div className="stat-value">{avgValue.toFixed(1)}</div>
              <div className="stat-label">Moy.</div>
            </div>
            <div className="stat-card">
              <div className="stat-value">{maxValue}</div>
              <div className="stat-label">Max</div>
            </div>
            <div className="stat-card">
              <div className="stat-value">{filteredPerfs.length}</div>
              <div className="stat-label">Total</div>
            </div>
          </div>

          <div className="chart-container">
            <div className="chart-title">Évolution</div>
            <div style={{ height: '240px' }}>
              <canvas ref={chartRef}></canvas>
            </div>
          </div>

          <div style={{ padding: '20px' }}>
            <div className="chart-title" style={{ marginBottom: '12px' }}>
              Historique ({filteredPerfs.length} entrées)
            </div>
            {filteredPerfs.slice(0, historyLimit).map(perf => (
              <div
                key={perf.id}
                className="exercise-card"
                onClick={() => onEditPerformance(perf, selectedExercise)}
                style={{ marginBottom: '8px', cursor: 'pointer' }}
              >
                <div className="exercise-info">
                  <div className="exercise-title">
                    {formatValue(perf.value, selectedExercise.unit, perf.weight, perf.series)}
                  </div>
                  <div className="exercise-subtitle">
                    {formatDate(perf.performed_at, 'full', user?.timezone || 'Europe/Paris')}
                  </div>
                  {perf.notes && (
                    <div className="exercise-value" style={{ fontStyle: 'italic' }}>
                      {perf.notes}
                    </div>
                  )}
                </div>
              </div>
            ))}
            {filteredPerfs.length > historyLimit && (
              <button
                className="btn btn-secondary"
                onClick={() => setHistoryLimit(prev => prev + 10)}
                style={{ width: '100%', marginTop: '12px' }}
              >
                Voir plus ({filteredPerfs.length - historyLimit} restantes)
              </button>
            )}
          </div>
        </>
      )}
    </div>
  );
}

// ============================================
// PROGRÈS « COURBES » (refonte du 2026-10-03)
// Répond d'abord à « est-ce que je progresse ? ». Les graphiques et la comparaison d'avant
// restent accessibles par le bouton « Graphiques » (ProgressClassic).
// ============================================
const SESSIONS_SHOWN = 6;
const SETS_SESSIONS_SHOWN = 4;

const pctChange = (cur, prev) => (prev > 0 ? Math.round(((cur - prev) / prev) * 100) : null);
const signedPct = (p) => (p > 0 ? `+${p} %` : `${String(p).replace('-', '−')} %`);

// Mini-courbe : la forme seulement (échelle propre à chaque exercice)
function Spark({ values, tone }) {
  if (values.length < 2) return <span className="spark-empty" />;
  const min = Math.min(...values);
  const max = Math.max(...values);
  const x = (i) => 4 + (i * 50) / (values.length - 1);
  const y = (v) => (max === min ? 13 : 22 - ((v - min) / (max - min)) * 18);
  const pts = values.map((v, i) => `${x(i)},${y(v)}`).join(' ');
  return (
    <svg className={`spark is-${tone}`} viewBox="0 0 58 26" aria-hidden="true">
      <polyline points={pts} />
      <circle cx={x(values.length - 1)} cy={y(values[values.length - 1])} r="3.5" />
    </svg>
  );
}

function ProgressHome({ user, exercises, performances, categories, onOpenExercise, onShowClassic, onShowStats, onManageCategories, onShowProfile }) {
  const tz = user?.timezone || TZ_DEFAULT;
  const today = getTodayInTimezone(tz);
  const groups = groupByCategory(exercises, categories);
  const names = [...categories.filter(c => !c.hidden).map(c => c.name), ...(groups['Autre'] ? ['Autre'] : [])]
    .filter(n => (groups[n] || []).some(ex => performances.some(p => p.exercise_id === ex.id)));
  const [picked, setPicked] = useState(() => localStorage.getItem('progress-category'));
  const selected = names.includes(picked) ? picked : names[0];
  const pick = (n) => { setPicked(n); try { localStorage.setItem('progress-category', n); } catch (e) { /* stockage indisponible */ } };

  const list = groups[selected] || [];
  const ids = new Set(list.map(ex => ex.id));

  // Séances de la catégorie = jours où au moins un de ses exercices a été fait
  const sessionsMap = {};
  performances.forEach(p => {
    if (!ids.has(p.exercise_id)) return;
    const d = dayOf(p.performed_at, tz);
    sessionsMap[d] = (sessionsMap[d] || 0) + calculatePerformanceVolume(p);
  });
  const sessions = Object.entries(sessionsMap).map(([day, volume]) => ({ day, volume })).sort((a, b) => a.day.localeCompare(b.day));
  const shown = sessions.slice(-SESSIONS_SHOWN);
  const lastS = sessions[sessions.length - 1];
  const prevS = sessions[sessions.length - 2];
  const delta = lastS && prevS ? pctChange(lastS.volume, prevS.volume) : null;
  const maxVol = Math.max(1, ...shown.map(s => s.volume));

  const rows = list.map(ex => {
    const perfs = performances.filter(p => p.exercise_id === ex.id);
    if (perfs.length === 0) return null;
    const vols = perfs.slice(0, SESSIONS_SHOWN).map(calculatePerformanceVolume).reverse();
    const d = perfs.length > 1 ? pctChange(calculatePerformanceVolume(perfs[0]), calculatePerformanceVolume(perfs[1])) : null;
    const best = perfs.length > 1 && calculatePerformanceVolume(perfs[0]) > Math.max(...perfs.slice(1).map(calculatePerformanceVolume));
    return { ex, perfs, vols, d, best, lastDay: dayOf(perfs[0].performed_at, tz) };
  }).filter(Boolean);
  const records = rows.filter(r => r.best && lastS && r.lastDay === lastS.day);

  const recordText = (r) => {
    const sets = parseSets(r.perfs[0]);
    const total = sets.reduce((t, s) => t + s.reps, 0);
    const weights = [...new Set(sets.map(s => s.weight))];
    if (r.ex.unit !== 'reps') return 'meilleure performance';
    return weights.length === 1 && weights[0] > 0 ? `${total} répétitions à ${kg(weights[0])} kg` : `${total} répétitions, volume record`;
  };

  return (
    <div className="progres pb-safe">
      <div className="progres-top">
        <h1 className="progres-h1">Progrès</h1>
        <div className="top-actions">
          <button className="top-btn is-label" onClick={onShowClassic} title="Graphiques et comparaison"><Icon name="chart" />Graphiques</button>
          <TopActions onShowStats={onShowStats} onManageCategories={onManageCategories} onShowProfile={onShowProfile} />
        </div>
      </div>

      {names.length === 0 ? (
        <p className="progres-empty">Tes progrès apparaîtront ici après ta première série notée.</p>
      ) : (
        <>
          {names.length > 1 && (
            <div className="seg" role="tablist" aria-label="Catégories">
              {names.map(n => <button key={n} role="tab" aria-selected={n === selected} className={n === selected ? 'is-on' : ''} onClick={() => pick(n)}>{n}</button>)}
            </div>
          )}

          {lastS && (
            <p className="lead">
              {whenLabel(lastS.day, today).replace(/^./, c => c.toUpperCase())}, ta séance {selected} a soulevé <b>{thousands(lastS.volume)}&nbsp;kg</b>
              {delta === null ? '.' : delta === 0 ? ', autant que la précédente.' : <>, <span className={delta > 0 ? 'is-up' : 'is-down'}>{Math.abs(delta)}&nbsp;% de {delta > 0 ? 'plus' : 'moins'}</span> que la précédente.</>}
            </p>
          )}

          {shown.length > 1 && (
            <div className="bars" aria-label="Volume des dernières séances">
              {shown.map((s, i) => (
                <div key={s.day} className={`bar ${i === shown.length - 1 ? 'is-last' : ''}`} style={{ '--h': `${Math.max(4, (s.volume / maxVol) * 100)}%` }}>
                  <span>{thousands(s.volume)}</span>
                  <em>{dayFmt(s.day, { day: 'numeric', month: 'short' })}</em>
                </div>
              ))}
            </div>
          )}

          {records.length > 0 && (
            <div className="records">
              {records.slice(0, 2).map(r => (
                <button key={r.ex.id} className="record" onClick={() => onOpenExercise(r.ex)}>
                  <small><Icon name="star" size={13} />Record</small>
                  <span>{r.ex.name}, {recordText(r)}</span>
                </button>
              ))}
            </div>
          )}

          <ul className="trend-list">
            {rows.map(r => {
              const tone = r.d === null || r.d === 0 ? 'flat' : r.d > 0 ? 'up' : 'down';
              return (
                <li key={r.ex.id}>
                  <button className="trend-row" onClick={() => onOpenExercise(r.ex)}>
                    <span className="trend-name">{r.ex.name}<small>{r.ex.unit === 'reps' ? parseSets(r.perfs[0]).map(setLabel).join('  ') : formatValue(r.perfs[0].value, r.ex.unit, r.perfs[0].weight, r.perfs[0].series)}</small></span>
                    <Spark values={r.vols} tone={tone} />
                    <span className={`trend-delta is-${tone}`}>{r.d === null ? '—' : signedPct(r.d)}</span>
                  </button>
                </li>
              );
            })}
          </ul>
          <p className="progres-note">Volume = répétitions × charge, comparé à la séance précédente de l'exercice.</p>
        </>
      )}
    </div>
  );
}

function ExerciseProgress({ user, exercise, performances, onBack, onEditPerformance, onLogSet }) {
  const tz = user?.timezone || TZ_DEFAULT;
  const today = getTodayInTimezone(tz);
  const [historyLimit, setHistoryLimit] = useState(10);
  const perfs = performances.filter(p => p.exercise_id === exercise.id);
  const recent = perfs.slice(0, SETS_SESSIONS_SHOWN).reverse();
  const sessionsSets = recent.map(p => ({ p, sets: parseSets(p) }));
  const maxReps = Math.max(1, ...sessionsSets.flatMap(s => s.sets.map(x => x.reps)));
  const weights = [...new Set(sessionsSets.flatMap(s => s.sets.map(x => x.weight)))].sort((a, b) => b - a);
  const shade = (w) => `w${Math.min(weights.indexOf(w), 3)}`;

  const lastSets = parseSets(perfs[0]);
  const prevSets = parseSets(perfs[1]);
  const total = (sets) => sets.reduce((t, s) => t + s.reps, 0);
  const top = (sets) => Math.max(0, ...sets.map(s => s.weight));
  const vDelta = perfs.length > 1 ? pctChange(calculatePerformanceVolume(perfs[0]), calculatePerformanceVolume(perfs[1])) : null;
  const target = exercise.unit === 'reps' ? targetOf(lastSets) : null;
  const firstDay = perfs.length ? dayOf(perfs[perfs.length - 1].performed_at, tz) : null;

  let insight = null;
  if (perfs.length > 1 && exercise.unit === 'reps') {
    const when = whenLabel(dayOf(perfs[0].performed_at, tz), today).replace(/^./, c => c.toUpperCase());
    const tw = top(lastSets), pw = top(prevSets);
    const moved = tw !== pw && tw > 0 && pw > 0
      ? `tu es ${tw > pw ? 'monté' : 'redescendu'} à ${kg(tw)} kg et `
      : (tw > 0 ? `à ${kg(tw)} kg, ` : '');
    const volume = vDelta === null || vDelta === 0 ? 'Le volume ne bouge pas.' : <>Le volume {vDelta > 0 ? 'monte' : 'baisse'} de <b>{Math.abs(vDelta)}&nbsp;%</b>.</>;
    insight = <>{when}, {moved}tu as fait <b>{total(lastSets)} répétitions</b> au lieu de {total(prevSets)}. {volume}</>;
  }

  return (
    <div className="progres pb-safe">
      <button className="back-link" onClick={onBack}>‹ Progrès</button>
      <h1 className="progres-title">{exercise.name}</h1>
      <p className="progres-sub">
        {perfs.length === 0 ? 'Aucune série notée pour l’instant.'
          : `${perfs.length} séance${perfs.length > 1 ? 's' : ''} depuis ${whenLabel(firstDay, today)}${exercise.unit === 'reps' ? ', répétitions par série' : ''}`}
      </p>

      {exercise.unit === 'reps' && sessionsSets.length > 0 && (
        <>
          <div className="sets-card">
            {sessionsSets.map(({ p, sets }, i) => (
              <div key={p.id} className={`sets-session ${i === sessionsSets.length - 1 ? 'is-last' : ''}`}>
                <div className="sets-chart">
                  {sets.map((s, j) => (
                    <i key={j} className={`set-bar ${shade(s.weight)}`} style={{ '--h': `${Math.max(6, (s.reps / maxReps) * 100)}%` }}><b>{s.reps}</b></i>
                  ))}
                </div>
                <p>{dayFmt(dayOf(p.performed_at, tz), { day: 'numeric', month: 'short' })}</p>
              </div>
            ))}
          </div>
          {weights.length > 0 && (
            <div className="sets-key">
              {weights.slice(0, 4).map(w => <span key={w}><i className={`set-bar ${shade(w)}`} />{w > 0 ? `${kg(w)} kg` : 'poids du corps'}</span>)}
            </div>
          )}
        </>
      )}

      {insight && <p className="insight">{insight}</p>}
      {target && (
        <div className="target">
          <small>À battre la prochaine fois</small>
          <p>{target.weight > 0 ? `${target.reps} × ${kg(target.weight)} kg` : `${target.reps} répétitions`}, sur une série</p>
        </div>
      )}

      <h2 className="history-title">Historique</h2>
      <ul className="history">
        {perfs.slice(0, historyLimit).map(p => (
          <li key={p.id}>
            <button onClick={() => onEditPerformance(p, exercise)}>
              <span>{dayFmt(dayOf(p.performed_at, tz), { weekday: 'short', day: 'numeric', month: 'short' })}</span>
              <b>{exercise.unit === 'reps' ? parseSets(p).map(setLabel).join('  ') : formatValue(p.value, exercise.unit, p.weight, p.series)}</b>
            </button>
          </li>
        ))}
      </ul>
      {perfs.length > historyLimit && (
        <button className="more-btn" onClick={() => setHistoryLimit(n => n + 10)}>Voir plus ({perfs.length - historyLimit} restantes)</button>
      )}
      <p className="progres-note">Touche une séance pour la corriger ou la supprimer.</p>

      <button className="seance-cta" onClick={() => onLogSet(exercise)}>Noter une série de {exercise.name}</button>
    </div>
  );
}

// Onglet Progrès : vue d'ensemble, détail d'un exercice, ou les graphiques d'avant
function ProgressView({ user, exercises, performances, categories, onEditPerformance, onDeletePerformance, onLogSet, onShowStats, onManageCategories, onShowProfile }) {
  const [mode, setMode] = useState('home');
  const [exerciseId, setExerciseId] = useState(null);
  const exercise = exercises.find(ex => ex.id === exerciseId);

  if (mode === 'classic') {
    return (
      <ProgressClassic
        user={user}
        exercises={exercises}
        performances={performances}
        onEditPerformance={onEditPerformance}
        onDeletePerformance={onDeletePerformance}
        onShowStats={onShowStats}
        onManageCategories={onManageCategories}
        onShowProfile={onShowProfile}
        onBack={() => setMode('home')}
      />
    );
  }
  if (mode === 'exercise' && exercise) {
    return (
      <ExerciseProgress
        user={user}
        exercise={exercise}
        performances={performances}
        onBack={() => setMode('home')}
        onEditPerformance={onEditPerformance}
        onLogSet={onLogSet}
      />
    );
  }
  return (
    <ProgressHome
      user={user}
      exercises={exercises}
      performances={performances}
      categories={categories}
      onOpenExercise={(ex) => { setExerciseId(ex.id); setMode('exercise'); window.scrollTo(0, 0); }}
      onShowClassic={() => setMode('classic')}
      onShowStats={onShowStats}
      onManageCategories={onManageCategories}
      onShowProfile={onShowProfile}
    />
  );
}

// Profile View (Modal)
function ProfileView({ user, onUpdateProfile, onLogout, onDeleteAccount, onClose }) {
  const [username, setUsername] = useState(user.username);
  const [email, setEmail] = useState(user.email);
  const [timezone, setTimezone] = useState(user.timezone || 'Europe/Paris');
  const [weightGoal, setWeightGoal] = useState(user.weight_goal || 'lose');
  const [currentPassword, setCurrentPassword] = useState('');
  const [newPassword, setNewPassword] = useState('');
  const [avatarUrl, setAvatarUrl] = useState(user.avatar_url || '');
  const [avatarChanged, setAvatarChanged] = useState(false);
  const [darkMode, setDarkMode] = useState(user.dark_mode === 1);
  const [editing, setEditing] = useState(false);
  const [message, setMessage] = useState('');
  const [showFeedback, setShowFeedback] = useState(false);
  const [feedbackMessage, setFeedbackMessage] = useState('');
  const [feedbackSending, setFeedbackSending] = useState(false);
  // Admin states
  const [showAdminUsers, setShowAdminUsers] = useState(false);
  const [showAdminFeedbacks, setShowAdminFeedbacks] = useState(false);
  const [adminUsers, setAdminUsers] = useState([]);
  const [adminFeedbacks, setAdminFeedbacks] = useState([]);
  const [adminLoading, setAdminLoading] = useState(false);

  const isAdmin = user.is_admin === 1 || user.is_admin === true;

  // Liste des timezones principales
  const timezones = [
    'Europe/Paris',
    'Europe/London',
    'Europe/Berlin',
    'Europe/Madrid',
    'Europe/Rome',
    'Europe/Brussels',
    'Europe/Amsterdam',
    'Europe/Lisbon',
    'Europe/Athens',
    'Europe/Moscow',
    'America/New_York',
    'America/Chicago',
    'America/Denver',
    'America/Los_Angeles',
    'America/Toronto',
    'America/Mexico_City',
    'America/Sao_Paulo',
    'America/Buenos_Aires',
    'Asia/Tokyo',
    'Asia/Shanghai',
    'Asia/Hong_Kong',
    'Asia/Singapore',
    'Asia/Dubai',
    'Asia/Kolkata',
    'Asia/Bangkok',
    'Asia/Seoul',
    'Australia/Sydney',
    'Australia/Melbourne',
    'Pacific/Auckland',
    'Africa/Cairo',
    'Africa/Johannesburg',
    'Africa/Lagos',
    'UTC'
  ];

  // Fonction pour gérer le toggle du mode sombre avec sauvegarde automatique
  const handleDarkModeToggle = async () => {
    const newDarkMode = !darkMode;
    setDarkMode(newDarkMode);

    // Appliquer immédiatement le changement visuel
    if (newDarkMode) {
      document.body.classList.add('dark-mode');
    } else {
      document.body.classList.remove('dark-mode');
    }

    // Sauvegarder automatiquement le changement
    try {
      await onUpdateProfile({
        username: user.username,
        email: user.email,
        dark_mode: newDarkMode ? 1 : 0
      });
    } catch (err) {
      // En cas d'erreur, restaurer l'état précédent
      setDarkMode(!newDarkMode);
      if (!newDarkMode) {
        document.body.classList.add('dark-mode');
      } else {
        document.body.classList.remove('dark-mode');
      }
      console.error('Erreur lors de la sauvegarde du mode sombre:', err);
    }
  };

  const loadAdminUsers = async () => {
    setAdminLoading(true);
    try {
      const users = await api.getAdminUsers();
      setAdminUsers(users);
    } catch (err) {
      console.error('Error loading users:', err);
    } finally {
      setAdminLoading(false);
    }
  };

  const loadAdminFeedbacks = async () => {
    setAdminLoading(true);
    try {
      const feedbacks = await api.getAdminFeedbacks();
      setAdminFeedbacks(feedbacks);
    } catch (err) {
      console.error('Error loading feedbacks:', err);
    } finally {
      setAdminLoading(false);
    }
  };

  const deleteFeedback = async (id) => {
    try {
      await api.deleteAdminFeedback(id);
      setAdminFeedbacks(prev => prev.filter(f => f.id !== id));
    } catch (err) {
      alert(getErrorMessage(err.serverMessage || err.message, false));
    }
  };

  const deleteUser = async (id, username) => {
    try {
      await api.deleteAdminUser(id);
      setAdminUsers(prev => prev.filter(u => u.id !== id));
    } catch (err) {
      alert(getErrorMessage(err.serverMessage || err.message, false));
    }
  };

  const handleFileSelect = (e) => {
    const file = e.target.files[0];
    if (file) {
      // Vérifier que c'est bien une image
      if (!file.type.startsWith('image/')) {
        setMessage('Erreur: Veuillez sélectionner une image');
        return;
      }

      // Vérifier la taille (max 5MB)
      if (file.size > 5 * 1024 * 1024) {
        setMessage('Erreur: L\'image ne doit pas dépasser 5MB');
        return;
      }

      // Convertir en base64
      const reader = new FileReader();
      reader.onloadend = () => {
        setAvatarUrl(reader.result);
        setAvatarChanged(true);
        setMessage('');
      };
      reader.onerror = () => {
        setMessage('Erreur lors de la lecture du fichier');
      };
      reader.readAsDataURL(file);
    }
  };

  const handleSave = async () => {
    try {
      const updateData = {
        username,
        email,
        timezone,
        weight_goal: weightGoal,
        currentPassword: currentPassword || undefined,
        newPassword: newPassword || undefined
      };

      // N'envoyer avatar_url que si elle a changé
      if (avatarChanged) {
        updateData.avatar_url = avatarUrl || null;
      }

      await onUpdateProfile(updateData);
      setMessage('Profil mis à jour');
      setCurrentPassword('');
      setNewPassword('');
      setAvatarChanged(false);
      setEditing(false);
    } catch (err) {
      const userMessage = getErrorMessage(err.serverMessage || err.message, false);
      setMessage('Erreur: ' + userMessage);
    }
  };

  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal fade-in" onClick={(e) => e.stopPropagation()} style={{ maxWidth: '400px' }}>
        <div className="profile-head">
          <button className="sheet-close" onClick={onClose} title="Fermer" aria-label="Fermer"><Icon name="close" /></button>
          <div className="profile-avatar">
            {avatarUrl ? <img src={avatarUrl} alt="Avatar" /> : username[0].toUpperCase()}
          </div>
          <h2 className="profile-name">{username}</h2>
          <p className="profile-mail">{user.email}</p>
        </div>

        {message && (
          <div className={`notice ${message.includes('Erreur') ? 'is-error' : 'is-ok'}`}>{message}</div>
        )}

        {editing ? (
          <div>
            <div className="form-group">
              <label className="form-label">Nom d'utilisateur</label>
              <input
                type="text"
                className="form-input"
                value={username}
                onChange={(e) => setUsername(e.target.value)}
              />
            </div>
            <div className="form-group">
              <label className="form-label">Adresse email</label>
              <input
                type="email"
                className="form-input"
                value={email}
                onChange={(e) => setEmail(e.target.value)}
                placeholder="votre@email.com"
              />
            </div>
            <div className="form-group">
              <label className="form-label">Timezone</label>
              <select
                className="form-input"
                value={timezone}
                onChange={(e) => setTimezone(e.target.value)}
                style={{ cursor: 'pointer' }}
              >
                {timezones.map(tz => (
                  <option key={tz} value={tz}>{tz}</option>
                ))}
              </select>
            </div>
            <div className="form-group">
              <label className="form-label">Objectif de poids</label>
              <select
                className="form-input"
                value={weightGoal}
                onChange={(e) => setWeightGoal(e.target.value)}
                style={{ cursor: 'pointer' }}
              >
                <option value="lose">Perdre du poids</option>
                <option value="gain">Prendre du poids</option>
                <option value="maintain">Maintenir le poids</option>
              </select>
            </div>
            <div className="form-group">
              <label className="form-label">Photo de profil</label>
              <div style={{ display: 'flex', gap: '8px', justifyContent: 'center' }}>
                <label htmlFor="avatar-file" className="btn btn-secondary" title="Choisir une photo">
                  <Icon name="camera" size={18} />Choisir une photo
                </label>
                <input
                  id="avatar-file"
                  type="file"
                  accept="image/*"
                  onChange={handleFileSelect}
                  style={{ display: 'none' }}
                />
                {avatarUrl && (
                  <button
                    type="button"
                    className="btn btn-secondary"
                    onClick={() => {
                      setAvatarUrl('');
                      setAvatarChanged(true);
                      setMessage('Photo de profil supprimée');
                    }}
                    title="Supprimer la photo"
                  >
                    <Icon name="trash" size={18} />Retirer
                  </button>
                )}
              </div>
            </div>
            <div className="form-group">
              <label className="form-label">Mot de passe actuel</label>
              <input
                type="password"
                className="form-input"
                value={currentPassword}
                onChange={(e) => setCurrentPassword(e.target.value)}
                placeholder="Obligatoire pour changer le mot de passe"
              />
            </div>
            <div className="form-group">
              <label className="form-label">Nouveau mot de passe</label>
              <input
                type="password"
                className="form-input"
                value={newPassword}
                onChange={(e) => setNewPassword(e.target.value)}
                placeholder="Optionnel"
              />
            </div>
            <div className="form-actions">
              <button className="btn btn-secondary" onClick={() => setEditing(false)}>
                Annuler
              </button>
              <button className="btn btn-primary" onClick={handleSave}>
                Enregistrer
              </button>
            </div>
          </div>
        ) : (
          <>
            <button
              className="btn btn-primary btn-block"
              onClick={() => setEditing(true)}
            >
              Modifier le profil
            </button>
            <button
              className="btn btn-secondary btn-block"
              onClick={() => {
                onLogout();
                onClose();
              }}
            >
              Se déconnecter
            </button>

            {/* Toggle Mode Sombre */}
            <div className="sheet-divider">
              <div
                className="toggle-container"
                onClick={handleDarkModeToggle}
                style={{ cursor: 'pointer' }}
              >
                <span className="toggle-label">Mode sombre</span>
                <div className={`toggle-switch ${darkMode ? 'active' : ''}`}>
                  <div className="toggle-slider"></div>
                </div>
              </div>
            </div>

            {isAdmin && (
              <>
                <div className="sheet-divider">
                  <p className="sheet-label">Administration</p>
                  <button
                    className="btn btn-secondary btn-block"
                    onClick={() => {
                      loadAdminUsers();
                      setShowAdminUsers(true);
                    }}
                  >
                    Voir les utilisateurs
                  </button>
                  <button
                    className="btn btn-secondary btn-block"
                    onClick={() => {
                      loadAdminFeedbacks();
                      setShowAdminFeedbacks(true);
                    }}
                  >
                    Voir les feedbacks
                  </button>
                </div>
              </>
            )}

            <button
              className="btn btn-secondary btn-block"
              onClick={() => setShowFeedback(true)}
            >
              Envoyer un feedback
            </button>
            <button
              className="btn btn-block is-danger"
              onClick={async () => {
                const confirm1 = window.confirm('Êtes-vous sûr de vouloir supprimer votre compte ?\n\nCette action est irréversible et supprimera toutes vos données.');
                if (!confirm1) return;

                const confirm2 = window.confirm('Dernière confirmation : voulez-vous vraiment supprimer définitivement votre compte ?');
                if (!confirm2) return;

                try {
                  await onDeleteAccount();
                } catch (err) {
                  console.error('Erreur suppression:', err);
                }
              }}
            >
              Supprimer mon compte
            </button>
          </>
        )}
      </div>

      {showFeedback && (
        <div className="modal-overlay" onClick={() => setShowFeedback(false)} style={{ zIndex: 1001 }}>
          <div className="modal fade-in" onClick={(e) => e.stopPropagation()} style={{ maxWidth: '400px' }}>
            <div className="modal-header">Envoyer un feedback</div>
            <p style={{ color: 'var(--chalk-soft)', marginBottom: '16px', fontSize: '14px' }}>
              Partagez vos suggestions, signaler un bug ou donner votre avis sur l'application.
            </p>
            <textarea
              className="form-input"
              value={feedbackMessage}
              onChange={(e) => setFeedbackMessage(e.target.value)}
              placeholder="Votre message..."
              rows={5}
              style={{ resize: 'vertical', minHeight: '100px' }}
            />
            <div className="form-actions" style={{ marginTop: '16px' }}>
              <button
                className="btn btn-secondary"
                onClick={() => {
                  setShowFeedback(false);
                  setFeedbackMessage('');
                }}
              >
                Annuler
              </button>
              <button
                className="btn btn-primary"
                disabled={!feedbackMessage.trim() || feedbackSending}
                onClick={async () => {
                  if (!feedbackMessage.trim()) return;
                  setFeedbackSending(true);
                  try {
                    await api.sendFeedback(feedbackMessage);
                    setShowFeedback(false);
                    setFeedbackMessage('');
                    setMessage('Feedback envoyé avec succès !');
                  } catch (err) {
                    alert(getErrorMessage(err.serverMessage || err.message, false));
                  } finally {
                    setFeedbackSending(false);
                  }
                }}
              >
                {feedbackSending ? 'Envoi...' : 'Envoyer'}
              </button>
            </div>
          </div>
        </div>
      )}

      {/* Modal Admin - Liste des utilisateurs */}
      {showAdminUsers && (
        <div className="modal-overlay" onClick={() => setShowAdminUsers(false)} style={{ zIndex: 1001 }}>
          <div className="modal fade-in" onClick={(e) => e.stopPropagation()} style={{ maxWidth: '500px', maxHeight: '80vh', display: 'flex', flexDirection: 'column' }}>
            <div className="modal-header">Utilisateurs inscrits</div>
            {adminLoading ? (
              <p style={{ textAlign: 'center', padding: '20px', color: 'var(--chalk-soft)' }}>Chargement...</p>
            ) : (
              <div style={{ overflowY: 'auto', flex: 1 }}>
                <p style={{ color: 'var(--chalk-soft)', marginBottom: '16px', fontSize: '14px' }}>
                  {adminUsers.length} utilisateur{adminUsers.length > 1 ? 's' : ''} inscrit{adminUsers.length > 1 ? 's' : ''}
                </p>
                {adminUsers.map(u => (
                  <div
                    key={u.id}
                    style={{
                      display: 'flex',
                      justifyContent: 'space-between',
                      alignItems: 'center',
                      padding: '12px',
                      background: 'var(--floor)',
                      borderRadius: '8px',
                      marginBottom: '8px'
                    }}
                  >
                    <div style={{ flex: 1 }}>
                      <div style={{ fontWeight: '600', display: 'flex', alignItems: 'center', gap: '8px' }}>
                        {u.username}
                        {u.is_admin === 1 && (
                          <span style={{
                            background: 'var(--yellow-wash)',
                            color: 'var(--yellow-ink)',
                            padding: '2px 6px',
                            borderRadius: '4px',
                            fontSize: '10px',
                            fontWeight: '700'
                          }}>ADMIN</span>
                        )}
                      </div>
                      <div className="admin-user-email" style={{ fontSize: '12px', color: 'var(--chalk-dim)' }}>{u.email}</div>
                    </div>
                    <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
                      <div className="admin-user-date" style={{ fontSize: '12px', color: 'var(--chalk-dim)', textAlign: 'right' }}>
                        {u.created_at ? new Date(u.created_at).toLocaleDateString('fr-FR', {
                          day: '2-digit',
                          month: '2-digit',
                          year: 'numeric',
                          timeZone: user?.timezone || 'Europe/Paris'
                        }) : 'N/A'}
                      </div>
                      {u.is_admin !== 1 && (
                        <button
                          className="action-btn delete"
                          onClick={() => {
                            if (confirm(`Supprimer le compte de "${u.username}" ?\n\nCette action supprimera toutes ses données.`)) {
                              deleteUser(u.id, u.username);
                            }
                          }}
                          style={{ padding: '6px 10px', fontSize: '12px' }}
                          title="Supprimer ce compte"
                        >
                          <Icon name="trash" size={18} />
                        </button>
                      )}
                    </div>
                  </div>
                ))}
              </div>
            )}
            <div className="form-actions" style={{ marginTop: '16px' }}>
              <button
                className="btn btn-primary"
                onClick={() => setShowAdminUsers(false)}
                style={{ width: '100%', justifyContent: 'center' }}
              >
                Fermer
              </button>
            </div>
          </div>
        </div>
      )}

      {/* Modal Admin - Liste des feedbacks */}
      {showAdminFeedbacks && (
        <div className="modal-overlay" onClick={() => setShowAdminFeedbacks(false)} style={{ zIndex: 1001 }}>
          <div className="modal fade-in" onClick={(e) => e.stopPropagation()} style={{ maxWidth: '500px', maxHeight: '80vh', display: 'flex', flexDirection: 'column' }}>
            <div className="modal-header">Feedbacks reçus</div>
            {adminLoading ? (
              <p style={{ textAlign: 'center', padding: '20px', color: 'var(--chalk-soft)' }}>Chargement...</p>
            ) : adminFeedbacks.length === 0 ? (
              <p style={{ textAlign: 'center', padding: '20px', color: 'var(--chalk-soft)' }}>Aucun feedback pour le moment</p>
            ) : (
              <div style={{ overflowY: 'auto', flex: 1 }}>
                <p style={{ color: 'var(--chalk-soft)', marginBottom: '16px', fontSize: '14px' }}>
                  {adminFeedbacks.length} feedback{adminFeedbacks.length > 1 ? 's' : ''}
                </p>
                {adminFeedbacks.map(f => (
                  <div
                    key={f.id}
                    style={{
                      padding: '12px',
                      background: 'var(--floor)',
                      borderRadius: '8px',
                      marginBottom: '8px'
                    }}
                  >
                    <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '8px' }}>
                      <div>
                        <span style={{ fontWeight: '600' }}>{f.username}</span>
                        <span className="admin-feedback-date" style={{ fontSize: '12px', color: 'var(--chalk-dim)', marginLeft: '8px' }}>
                          {new Date(f.created_at).toLocaleDateString('fr-FR', {
                            day: '2-digit',
                            month: '2-digit',
                            year: 'numeric',
                            hour: '2-digit',
                            minute: '2-digit',
                            timeZone: user?.timezone || 'Europe/Paris'
                          })}
                        </span>
                      </div>
                      <button
                        className="action-btn delete"
                        onClick={() => {
                          if (confirm('Supprimer ce feedback ?')) {
                            deleteFeedback(f.id);
                          }
                        }}
                        style={{ padding: '4px 8px', fontSize: '12px' }}
                      >
                        <Icon name="trash" size={18} />
                      </button>
                    </div>
                    <p className="admin-feedback-text" style={{ color: 'var(--chalk)', fontSize: '14px', lineHeight: '1.5', whiteSpace: 'pre-wrap' }}>
                      {f.message}
                    </p>
                  </div>
                ))}
              </div>
            )}
            <div className="form-actions" style={{ marginTop: '16px' }}>
              <button
                className="btn btn-primary"
                onClick={() => setShowAdminFeedbacks(false)}
                style={{ width: '100%', justifyContent: 'center' }}
              >
                Fermer
              </button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

// Bottom Navigation
function BottomNav({ currentView, onViewChange }) {
  const navItems = [
    { id: 'dashboard', label: 'Séance', path: 'M6 8v8M18 8v8M3 10v4M21 10v4M6 12h12' },
    { id: 'progress', label: 'Progrès', path: 'M3 17l6-6 4 4 8-8M15 7h6v6' },
    { id: 'weight', label: 'Poids', path: 'M5 20h14l-2-12H7zM9 8a3 3 0 0 1 6 0' },
    { id: 'calendar', label: 'Calendrier', path: 'M5 5h14a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2zM3 10h18M8 3v4M16 3v4' }
  ];

  return (
    <nav className="bottom-nav">
      {navItems.map(item => (
        <button
          key={item.id}
          className={`nav-item ${currentView === item.id ? 'active' : ''}`}
          onClick={() => onViewChange(item.id)}
          aria-current={currentView === item.id ? 'page' : undefined}
        >
          <svg className="nav-icon" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d={item.path} /></svg>
          <span className="nav-label">{item.label}</span>
        </button>
      ))}
    </nav>
  );
}

// Calendar View — refonte « Plaques » (2026-10-03) : un jour de séance prend la couleur de la catégorie
// travaillée, comme la semaine de l'accueil. Toucher un jour fait tourner son statut (fait → manqué → rien).
const CAL_MONTHS = ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'];
const CAL_STATUS = { done: 'séance faite', missed: 'séance manquée', none: 'rien de noté' };

// Couleur de catégorie dominante de chaque jour d'entraînement
const categoryByDay = (performances, exercises, tz) => {
  const exById = {};
  exercises.forEach(ex => { exById[ex.id] = ex; });
  const counts = {};
  performances.forEach(p => {
    const d = dayOf(p.performed_at, tz);
    const cat = (exById[p.exercise_id]?.categories || [])[0];
    const key = cat ? cat.name : 'Autre';
    counts[d] = counts[d] || {};
    counts[d][key] = counts[d][key] || { name: key, color: cat ? cat.color : null, n: 0 };
    counts[d][key].n++;
  });
  const out = {};
  Object.entries(counts).forEach(([d, byCat]) => { out[d] = Object.values(byCat).sort((a, b) => b.n - a.n)[0]; });
  return out;
};

function CalendarView({ user, calendarDays, performances, exercises = [], onUpdateDay, onShowStats, onManageCategories, onShowProfile }) {
  const tz = user?.timezone || TZ_DEFAULT;
  const today = getTodayInTimezone(tz);
  const [currentDate, setCurrentDate] = useState(() => {
    const savedDate = localStorage.getItem('calendar-view-current-date');
    return savedDate ? new Date(savedDate) : new Date();
  });
  const [monthDays, setMonthDays] = useState(calendarDays);
  const [touchStart, setTouchStart] = useState(null);
  const [touchEnd, setTouchEnd] = useState(null);
  const calendarGridRef = useRef(null);

  useEffect(() => {
    localStorage.setItem('calendar-view-current-date', currentDate.toISOString());
  }, [currentDate]);

  const year = currentDate.getFullYear();
  const month = currentDate.getMonth();
  const daysInMonth = new Date(year, month + 1, 0).getDate();
  const offset = (new Date(year, month, 1).getDay() + 6) % 7; // semaine du lundi au dimanche

  // Statuts du mois affiché (avant, seuls ceux du mois en cours étaient chargés)
  useEffect(() => {
    let alive = true;
    api.request(`/api/calendar?month=${month + 1}&year=${year}`)
      .then(days => { if (alive && Array.isArray(days)) setMonthDays(days); })
      .catch(err => console.error('Calendar load error:', err));
    return () => { alive = false; };
  }, [year, month, calendarDays]);

  const goToPreviousMonth = () => setCurrentDate(new Date(year, month - 1, 1));
  const goToNextMonth = () => setCurrentDate(new Date(year, month + 1, 1));

  const onTouchStart = (e) => { setTouchEnd(null); setTouchStart(e.targetTouches[0].clientX); };
  const onTouchMove = (e) => setTouchEnd(e.targetTouches[0].clientX);
  const onTouchEnd = () => {
    if (!touchStart || !touchEnd) return;
    const distance = touchStart - touchEnd;
    if (distance > 50) goToNextMonth();
    else if (distance < -50) goToPreviousMonth();
  };

  useEffect(() => {
    const grid = calendarGridRef.current;
    if (!grid) return;
    grid.addEventListener('touchstart', onTouchStart);
    grid.addEventListener('touchmove', onTouchMove);
    grid.addEventListener('touchend', onTouchEnd);
    return () => {
      grid.removeEventListener('touchstart', onTouchStart);
      grid.removeEventListener('touchmove', onTouchMove);
      grid.removeEventListener('touchend', onTouchEnd);
    };
  }, [touchStart, touchEnd]);

  const dateOf = (day) => `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
  const statusOf = (day) => monthDays.find(d => d.date === dateOf(day))?.status || 'none';

  const handleDayClick = (day) => {
    const current = statusOf(day);
    const next = current === 'none' ? 'done' : current === 'done' ? 'missed' : 'none';
    // Affichage immédiat ; le rechargement du mois suit l'enregistrement
    setMonthDays(prev => [...prev.filter(d => d.date !== dateOf(day)), { date: dateOf(day), status: next }]);
    onUpdateDay(dateOf(day), next);
  };

  const cats = useMemo(() => categoryByDay(performances, exercises, tz), [performances, exercises, tz]);
  const doneCount = Array.from({ length: daysInMonth }, (_, i) => statusOf(i + 1)).filter(s => s === 'done').length;
  const missedCount = Array.from({ length: daysInMonth }, (_, i) => statusOf(i + 1)).filter(s => s === 'missed').length;

  return (
    <div className="cal pb-safe">
      <div className="progres-top">
        <h1 className="progres-h1">Calendrier</h1>
        <TopActions onShowStats={onShowStats} onManageCategories={onManageCategories} onShowProfile={onShowProfile} />
      </div>

      <div className="cal-head">
        <button className="top-btn" onClick={goToPreviousMonth} aria-label="Mois précédent">‹</button>
        <p className="cal-month">{CAL_MONTHS[month]} {year}</p>
        <button className="top-btn" onClick={goToNextMonth} aria-label="Mois suivant">›</button>
      </div>
      <p className="cal-sum">
        {doneCount === 0 ? 'Aucune séance notée ce mois-ci.' : `${doneCount} séance${doneCount > 1 ? 's' : ''} ce mois-ci`}
        {missedCount > 0 ? `, ${missedCount} manquée${missedCount > 1 ? 's' : ''}.` : doneCount > 0 ? '.' : ''}
      </p>

      <div className="cal-grid" ref={calendarGridRef}>
        {WEEK_LETTERS.map((l, i) => <span key={i} className="cal-dow">{l}</span>)}
        {Array.from({ length: offset }, (_, i) => <span key={`e${i}`} />)}
        {Array.from({ length: daysInMonth }, (_, i) => {
          const day = i + 1;
          const status = statusOf(day);
          const cat = status === 'done' ? cats[dateOf(day)] : null;
          return (
            <button
              key={day}
              className={`cal-day is-${status} ${dateOf(day) === today ? 'is-today' : ''}`}
              style={status === 'done' ? { '--disc': (cat && cat.color) || 'var(--chalk-soft)' } : undefined}
              onClick={() => handleDayClick(day)}
              aria-label={`${day} ${CAL_MONTHS[month]} : ${CAL_STATUS[status]}${cat ? ', ' + cat.name : ''}`}
              title={cat ? cat.name : undefined}
            >
              {day}
            </button>
          );
        })}
      </div>

      <div className="cal-legend">
        <span><i className="cal-day is-done cal-sample" />Séance faite, à la couleur de sa catégorie</span>
        <span><i className="cal-day is-missed cal-sample" />Séance manquée</span>
        <span><i className="cal-day is-none cal-sample" />Rien de noté</span>
      </div>
      <p className="progres-note">Touche un jour pour changer son statut. Glisse pour changer de mois.</p>
    </div>
  );
}

// Edit Weight Modal
function EditWeightModal({ weight, user, onClose, onSave, onDelete }) {
  const [editedWeight, setEditedWeight] = useState(weight.weight);

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!editedWeight) return;
    await onSave(weight.id, parseFloat(editedWeight));
    onClose();
  };

  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal fade-in" onClick={(e) => e.stopPropagation()}>
        <div className="modal-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
          <span>Modifier le poids</span>
          <button
            type="button"
            className="action-btn delete"
            onClick={() => {
              if (confirm('Supprimer cette entrée de poids ?')) {
                onDelete(weight.id);
                onClose();
              }
            }}
            title="Supprimer"
            style={{ position: 'relative', top: 0, right: 0 }}
          >
            <Icon name="trash" size={18} />
          </button>
        </div>
        <form onSubmit={handleSubmit}>
          <div className="form-group">
            <label className="form-label">Date</label>
            <input
              type="text"
              className="form-input"
              value={formatDate(weight.date, 'full', user?.timezone || 'Europe/Paris')}
              disabled
            />
          </div>

          <div className="form-group">
            <label className="form-label">Poids (kg)</label>
            <div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
              <button
                type="button"
                className="btn btn-secondary"
                onClick={() => setEditedWeight(Math.max(0, editedWeight ? parseFloat(editedWeight) - 0.5 : 0))}
                style={{ width: '48px', height: '48px', fontSize: '24px', padding: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' }}
              >
                −
              </button>
              <input
                type="number"
                step="0.1"
                className="form-input"
                value={editedWeight}
                onChange={(e) => setEditedWeight(e.target.value)}
                placeholder="70.5"
                style={{ flex: 1, textAlign: 'center', fontSize: '20px', fontWeight: 'bold' }}
                autoFocus
              />
              <button
                type="button"
                className="btn btn-secondary"
                onClick={() => setEditedWeight(editedWeight ? parseFloat(editedWeight) + 0.5 : 0.5)}
                style={{ width: '48px', height: '48px', fontSize: '24px', padding: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' }}
              >
                +
              </button>
            </div>
          </div>

          <div className="form-actions">
            <button type="button" className="btn btn-secondary" onClick={onClose}>
              Annuler
            </button>
            <button type="submit" className="btn btn-primary">
              Enregistrer
            </button>
          </div>
        </form>
      </div>
    </div>
  );
}

// Couleurs des graphiques prises dans le thème courant (clair ou sombre)
const chartTheme = () => {
  const css = getComputedStyle(document.body);
  const get = (name, fallback) => (css.getPropertyValue(name) || '').trim() || fallback;
  const blue = get('--blue', '#2F6BD6');
  const green = get('--green', '#2E9E5B');
  return { blue, green, blueWash: blue + '22', greenWash: green + '22', text: get('--chalk-dim', '#848B95'), grid: get('--line', '#D8DCE1') };
};

// Weight Tracker View
function WeightTrackerView({ user, onAddWeight, onDeleteWeight, onShowStats, onManageCategories, onShowProfile }) {
  const [weights, setWeights] = useState([]);
  const [performances, setPerformances] = useState([]);
  const [newWeight, setNewWeight] = useState('');
  const [showEditModal, setShowEditModal] = useState(false);
  const [editingWeight, setEditingWeight] = useState(null);
  const [loading, setLoading] = useState(true);
  const [historyLimit, setHistoryLimit] = useState(10);
  const [weightGoal, setWeightGoal] = useState(user.weight_goal || 'lose');
  const [showCorrelation, setShowCorrelation] = useState(() => {
    const savedCorrelation = localStorage.getItem('weight-tracker-show-correlation');
    return savedCorrelation === 'true';
  });
  const [showCorrelationInfo, setShowCorrelationInfo] = useState(false);
  const chartRef = useRef(null);
  const chartInstance = useRef(null);
  const correlationChartRef = useRef(null);
  const correlationChartInstance = useRef(null);

  // Sauvegarder l'état de corrélation dans localStorage
  useEffect(() => {
    localStorage.setItem('weight-tracker-show-correlation', showCorrelation.toString());
  }, [showCorrelation]);

  useEffect(() => {
    loadWeights();
    loadPerformances();
  }, []);

  const loadPerformances = async () => {
    try {
      const response = await api.getPerformances();
      setPerformances(response);
    } catch (err) {
      console.error('Error loading performances:', err);
    }
  };
  
  const loadWeights = async () => {
    try {
      const response = await api.request('/api/weights');
      setWeights(response);
      setLoading(false);
    } catch (err) {
      console.error('Error loading weights:', err);
      setLoading(false);
    }
  };
  
  useEffect(() => {
    if (weights.length > 0 && chartRef.current) {
      const ctx = chartRef.current.getContext('2d');
      const c = chartTheme();
      
      if (chartInstance.current) {
        chartInstance.current.destroy();
      }
      
      const data = weights.slice().reverse().slice(-30);
      
      chartInstance.current = new Chart(ctx, {
        type: 'line',
        data: {
          labels: data.map(w => formatDate(w.date, 'short', user?.timezone || 'Europe/Paris')),
          datasets: [{
            label: 'Poids (kg)',
            data: data.map(w => w.weight),
            borderColor: c.blue,
            backgroundColor: c.blueWash,
            tension: 0.4,
            fill: true,
            pointRadius: 6,
            pointHoverRadius: 10,
            pointHitRadius: 15
          }]
        },
        options: {
          responsive: true,
          maintainAspectRatio: false,
          interaction: {
            mode: 'nearest',
            intersect: false,
            axis: 'x'
          },
          plugins: {
            legend: { display: false },
            tooltip: {
              enabled: true,
              mode: 'nearest',
              intersect: false,
              backgroundColor: 'rgba(0, 0, 0, 0.8)',
              padding: 12,
              displayColors: true,
              callbacks: {
                title: function(context) {
                  return (context && context[0] && context[0].label) || '';
                },
                label: function(context) {
                  return context.dataset.label + ': ' + context.parsed.y + ' kg';
                }
              }
            }
          },
          scales: {
            y: {
              beginAtZero: false,
              grid: { color: c.grid },
              ticks: { color: c.text }
            },
            x: {
              grid: { color: c.grid },
              ticks: { color: c.text }
            }
          }
        }
      });
    }
    
    return () => {
      if (chartInstance.current) {
        chartInstance.current.destroy();
      }
    };
  }, [weights, user?.dark_mode]);

  // Graphique de corrélation poids vs performances
  useEffect(() => {
    if (weights.length > 0 && performances.length > 0 && showCorrelation && correlationChartRef.current) {
      const ctx = correlationChartRef.current.getContext('2d');
      const c = chartTheme();

      if (correlationChartInstance.current) {
        correlationChartInstance.current.destroy();
      }

      // Regrouper les performances par date et calculer le volume total quotidien
      const performancesByDate = {};
      performances.forEach(p => {
        const date = p.performed_at.split('T')[0];
        if (!performancesByDate[date]) {
          performancesByDate[date] = { totalVolume: 0, totalReps: 0, count: 0 };
        }
        const volume = calculatePerformanceVolume(p);
        performancesByDate[date].totalVolume += volume;
        performancesByDate[date].totalReps += p.value;
        performancesByDate[date].count += 1;
      });

      // Préparer les données pour les 30 derniers jours
      const last30Days = [];
      const today = new Date();
      for (let i = 29; i >= 0; i--) {
        const date = new Date(today);
        date.setDate(date.getDate() - i);
        const dateStr = date.toISOString().split('T')[0];
        last30Days.push(dateStr);
      }

      // Mapper les données de poids et performances
      const weightData = [];
      const volumeData = [];
      const labels = [];

      last30Days.forEach(dateStr => {
        // Trouver le poids le plus proche pour cette date
        const weightEntry = weights.find(w => w.date.split('T')[0] === dateStr) ||
                           weights.find(w => new Date(w.date) <= new Date(dateStr));

        // Récupérer les performances pour cette date
        const perfData = performancesByDate[dateStr];

        if (weightEntry || perfData) {
          labels.push(formatDate(dateStr, 'short', user?.timezone || 'Europe/Paris'));
          weightData.push(weightEntry ? weightEntry.weight : null);
          volumeData.push(perfData ? perfData.totalVolume : null);
        }
      });

      correlationChartInstance.current = new Chart(ctx, {
        type: 'line',
        data: {
          labels: labels,
          datasets: [
            {
              label: 'Poids (kg)',
              data: weightData,
              borderColor: c.blue,
              backgroundColor: c.blueWash,
              yAxisID: 'y',
              tension: 0.4,
              fill: false,
              pointRadius: 4,
              pointHoverRadius: 8,
              pointHitRadius: 12,
              spanGaps: true
            },
            {
              label: 'Volume total',
              data: volumeData,
              borderColor: c.green,
              backgroundColor: c.greenWash,
              yAxisID: 'y1',
              tension: 0.4,
              fill: false,
              pointRadius: 4,
              pointHoverRadius: 8,
              pointHitRadius: 12,
              spanGaps: true
            }
          ]
        },
        options: {
          responsive: true,
          maintainAspectRatio: false,
          interaction: {
            mode: 'index',
            intersect: false
          },
          plugins: {
            legend: {
              display: true,
              position: 'top',
              labels: {
                boxWidth: 12,
                padding: 8,
                font: { size: 11 }
              }
            },
            tooltip: {
              enabled: true,
              mode: 'index',
              intersect: false,
              backgroundColor: 'rgba(0, 0, 0, 0.8)',
              padding: 12,
              displayColors: true,
              callbacks: {
                title: function(context) {
                  return (context && context[0] && context[0].label) || '';
                },
                label: function(context) {
                  let label = context.dataset.label || '';
                  if (label) {
                    label += ': ';
                  }
                  if (context.parsed.y !== null) {
                    label += context.parsed.y;
                    if (context.dataset.yAxisID === 'y') {
                      label += ' kg';
                    }
                  } else {
                    label += '-';
                  }
                  return label;
                }
              }
            }
          },
          scales: {
            y: {
              type: 'linear',
              display: true,
              position: 'left',
              beginAtZero: false,
              grid: { color: c.grid },
              ticks: { color: c.blue },
              title: {
                display: true,
                text: 'Poids (kg)',
                color: c.blue
              }
            },
            y1: {
              type: 'linear',
              display: true,
              position: 'right',
              beginAtZero: true,
              grid: { display: false },
              ticks: { color: c.green },
              title: {
                display: true,
                text: 'Volume total',
                color: c.green
              }
            },
            x: {
              grid: { color: c.grid },
              ticks: { color: c.text, maxRotation: 45, minRotation: 45 }
            }
          }
        }
      });
    }

    return () => {
      if (correlationChartInstance.current) {
        correlationChartInstance.current.destroy();
      }
    };
  }, [weights, performances, showCorrelation, user?.dark_mode]);

  const handleAddWeight = async (e) => {
    e.preventDefault();
    if (!newWeight) return;
    
    try {
      await api.request('/api/weights', {
        method: 'POST',
        body: JSON.stringify({ weight: parseFloat(newWeight) })
      });
      setNewWeight('');
      loadWeights();
    } catch (err) {
      alert(getErrorMessage(err.serverMessage || err.message, false));
    }
  };

  const handleEditWeight = (weight) => {
    setEditingWeight(weight);
    setShowEditModal(true);
  };

  const handleSaveWeight = async (id, newWeight) => {
    try {
      await api.request(`/api/weights/${id}`, {
        method: 'PUT',
        body: JSON.stringify({ weight: newWeight })
      });
      loadWeights();
    } catch (err) {
      alert(getErrorMessage(err.serverMessage || err.message, false));
    }
  };

  const handleDeleteWeight = async (id) => {
    if (!confirm('Supprimer cette entrée ?')) return;

    try {
      await api.request(`/api/weights/${id}`, { method: 'DELETE' });
      loadWeights();
    } catch (err) {
      alert(getErrorMessage(err.serverMessage || err.message, false));
    }
  };

  const handleWeightGoalChange = async (newGoal) => {
    setWeightGoal(newGoal);
    try {
      await api.request('/api/user/profile', {
        method: 'PUT',
        body: JSON.stringify({
          username: user.username,
          email: user.email,
          weight_goal: newGoal
        })
      });
    } catch (err) {
      console.error('Erreur lors de la sauvegarde de l\'objectif:', err);
      // Restaurer l'ancienne valeur en cas d'erreur
      setWeightGoal(user.weight_goal || 'lose');
    }
  };

  const currentWeight = weights.length > 0 ? weights[0].weight : null;
  const previousWeight = weights.length > 1 ? weights[1].weight : null;
  const weightChange = currentWeight && previousWeight ? (currentWeight - previousWeight).toFixed(1) : null;

  const tz = user?.timezone || TZ_DEFAULT;
  const changeNum = weightChange === null ? null : parseFloat(weightChange);
  // Bon sens selon l'objectif : vert si ça va dans le bon sens, orangé sinon, gris si ça ne bouge pas
  const changeTone = (() => {
    if (changeNum === null || changeNum === 0) return 'flat';
    if (weightGoal === 'lose') return changeNum < 0 ? 'up' : 'down';
    if (weightGoal === 'gain') return changeNum > 0 ? 'up' : 'down';
    return 'flat';
  })();
  const GOALS = [['lose', 'Perdre'], ['maintain', 'Maintenir'], ['gain', 'Prendre']];

  return (
    <div className="poids pb-safe">
      <div className="progres-top">
        <h1 className="progres-h1">Poids</h1>
        <TopActions onShowStats={onShowStats} onManageCategories={onManageCategories} onShowProfile={onShowProfile} />
      </div>

      {currentWeight ? (
        <section className="poids-hero">
          <p className="poids-now"><b>{kg(currentWeight)}</b><span>kg</span></p>
          <p className="poids-meta">Dernière pesée : {whenLabel(dayOf(weights[0].date, tz), getTodayInTimezone(tz))}</p>
          {changeNum !== null && (
            <p className={`poids-change is-${changeTone}`}>
              {changeNum === 0 ? 'Stable' : `${changeNum > 0 ? '+' : '−'}${kg(Math.abs(changeNum))} kg`} depuis la pesée précédente
            </p>
          )}
        </section>
      ) : !loading && (
        <p className="poids-empty">Aucune pesée pour l'instant. Note ton poids juste en dessous.</p>
      )}

      <form className="poids-add" onSubmit={handleAddWeight}>
        <label className="poids-label" htmlFor="poids-input">Nouvelle pesée</label>
        <div className="poids-add-row">
          <input
            id="poids-input"
            className="poids-input"
            type="number"
            step="0.1"
            inputMode="decimal"
            value={newWeight}
            onChange={(e) => setNewWeight(e.target.value)}
            placeholder={currentWeight ? String(currentWeight) : '70.5'}
          />
          <span className="poids-unit">kg</span>
          <button type="submit" className="poids-btn" disabled={!newWeight}>Ajouter</button>
        </div>
      </form>

      <div className="poids-goal">
        <p className="poids-label">Mon objectif</p>
        <div className="seg" role="radiogroup" aria-label="Mon objectif">
          {GOALS.map(([value, label]) => (
            <button key={value} role="radio" aria-checked={weightGoal === value} className={weightGoal === value ? 'is-on' : ''} onClick={() => handleWeightGoalChange(value)}>{label}</button>
          ))}
        </div>
      </div>

      {weights.length > 0 && (
        <section className="chart-card">
          <h2 className="card-title">Évolution</h2>
          <div className="chart-box"><canvas ref={chartRef}></canvas></div>
        </section>
      )}

      {weights.length > 0 && performances.length > 0 && (
        <button className="switch-row" onClick={() => setShowCorrelation(!showCorrelation)} aria-pressed={showCorrelation}>
          <span>Comparer avec le volume de tes séances</span>
          <i className={`switch ${showCorrelation ? 'is-on' : ''}`} />
        </button>
      )}

      {weights.length > 0 && performances.length > 0 && showCorrelation && (
        <section className="chart-card is-relative">
          <div className="card-head">
            <h2 className="card-title">Poids et volume</h2>
            <button className="info-btn" onClick={() => setShowCorrelationInfo(!showCorrelationInfo)} aria-expanded={showCorrelationInfo} aria-label="Comment lire ce graphique">i</button>
          </div>
          {showCorrelationInfo && (
            <div className="info-pop">
              <p className="info-pop-title">Comment lire ce graphique</p>
              <p><b className="is-blue">Courbe bleue, axe de gauche :</b> ton poids en kg.</p>
              <p><b className="is-green">Courbe verte, axe de droite :</b> le volume de tes séances (répétitions × charges).</p>
              <p className="info-pop-note">Sur les 30 derniers jours : tu vois si ta force suit ton poids.</p>
              <button className="info-pop-close" onClick={() => setShowCorrelationInfo(false)}>Compris</button>
            </div>
          )}
          <div className="chart-box is-tall"><canvas ref={correlationChartRef}></canvas></div>
        </section>
      )}

      <h2 className="history-title">Historique{weights.length > 0 ? ` (${weights.length})` : ''}</h2>
      {weights.length === 0 ? (
        <p className="progres-note">Tes pesées apparaîtront ici.</p>
      ) : (
        <>
          <ul className="history">
            {weights.slice(0, historyLimit).map(w => (
              <li key={w.id}>
                <button onClick={() => handleEditWeight(w)}>
                  <span>{formatDate(w.date, 'full', tz)}</span>
                  <b>{kg(w.weight)} kg</b>
                </button>
              </li>
            ))}
          </ul>
          {weights.length > historyLimit && (
            <button className="more-btn" onClick={() => setHistoryLimit(prev => prev + 10)}>Voir plus ({weights.length - historyLimit} restantes)</button>
          )}
          <p className="progres-note">Touche une pesée pour la corriger ou la supprimer.</p>
        </>
      )}

      {showEditModal && editingWeight && (
        <EditWeightModal
          weight={editingWeight}
          user={user}
          onClose={() => {
            setShowEditModal(false);
            setEditingWeight(null);
          }}
          onSave={handleSaveWeight}
          onDelete={async (id) => {
            try {
              await api.request(`/api/weights/${id}`, { method: 'DELETE' });
              loadWeights();
            } catch (err) {
              alert(getErrorMessage(err.serverMessage || err.message, false));
            }
          }}
        />
      )}
    </div>
  );
}

// Category Manager Modal
function CategoryManagerModal({ categories, onClose, onSave, onDelete, onCreate, onReorder, onToggleHidden }) {
  const [editingId, setEditingId] = useState(null);
  const [editName, setEditName] = useState('');
  const [editColor, setEditColor] = useState('');
  const [showAddForm, setShowAddForm] = useState(false);
  const [newName, setNewName] = useState('');
  const [newColor, setNewColor] = useState('#3b82f6');
  const [localCategories, setLocalCategories] = useState(() =>
    [...categories].sort((a, b) => a.position - b.position)
  );
  const [deletedIds, setDeletedIds] = useState([]);
  const [hasChanges, setHasChanges] = useState(false);
  const categoriesListRef = useRef(null);
  const sortableInstanceRef = useRef(null);
  const tempIdCounter = useRef(-1);

  const colorOptions = [
    '#ef4444', '#f59e0b', '#10b981', '#3b82f6', '#8b5cf6',
    '#ec4899', '#14b8a6', '#f97316', '#06b6d4', '#6366f1'
  ];

  // Mettre à jour les catégories locales quand les catégories props changent
  useEffect(() => {
    setLocalCategories([...categories].sort((a, b) => a.position - b.position));
    setDeletedIds([]);
    setHasChanges(false);
  }, [categories]);

  const handleEdit = (category) => {
    setEditingId(category.id);
    setEditName(category.name);
    setEditColor(category.color);
  };

  const handleSave = (id) => {
    // Mettre à jour localement seulement
    setLocalCategories(prev =>
      prev.map(cat =>
        cat.id === id
          ? { ...cat, name: editName, color: editColor }
          : cat
      )
    );
    setEditingId(null);
    setHasChanges(true);
  };

  const handleCreate = (e) => {
    e.preventDefault();
    if (!newName) return;

    // Créer une nouvelle catégorie avec un ID temporaire
    const tempId = tempIdCounter.current--;
    const newCategory = {
      id: tempId,
      name: newName,
      color: newColor,
      position: localCategories.length,
      isNew: true
    };

    setLocalCategories(prev => [...prev, newCategory]);
    setNewName('');
    setNewColor('#3b82f6');
    setShowAddForm(false);
    setHasChanges(true);
  };

  // Masquage local, appliqué à l'enregistrement comme le reste
  const handleToggleHidden = (id) => {
    setLocalCategories(prev =>
      prev.map(cat => cat.id === id ? { ...cat, hidden: cat.hidden ? 0 : 1 } : cat)
    );
    setHasChanges(true);
  };

  const handleDelete = (id) => {
    if (!confirm('Supprimer cette catégorie ? Tous les exercices de cette catégorie seront également supprimés définitivement.')) return;

    // Retirer de la liste locale
    setLocalCategories(prev => prev.filter(cat => cat.id !== id));

    // Si c'est une catégorie existante (pas nouvellement créée), l'ajouter aux suppressions
    if (id > 0) {
      setDeletedIds(prev => [...prev, id]);
    }

    setHasChanges(true);
  };

  const handleSaveAll = async () => {
    try {
      if (!hasChanges) {
        onClose();
        return;
      }

      // Recalculer toutes les positions pour qu'elles soient séquentielles
      const categoriesWithNewPositions = localCategories.map((cat, index) => ({
        ...cat,
        position: index
      }));

      // 1. Supprimer les catégories supprimées
      for (const id of deletedIds) {
        await onDelete(id);
      }

      // 2. Mettre à jour les catégories existantes (nom, couleur ET position)
      const originalCatsMap = new Map(categories.map(c => [c.id, c]));
      for (const cat of categoriesWithNewPositions.filter(c => !c.isNew)) {
        await onSave(cat.id, {
          name: cat.name,
          color: cat.color,
          position: cat.position
        });
        if (!!cat.hidden !== !!originalCatsMap.get(cat.id)?.hidden) {
          await onToggleHidden(cat.id, !!cat.hidden);
        }
      }

      // 3. Créer les nouvelles catégories avec leur position
      for (const cat of categoriesWithNewPositions.filter(c => c.isNew)) {
        await onCreate({
          name: cat.name,
          color: cat.color,
          position: cat.position
        });
      }

      onClose();
    } catch (err) {
      console.error('Erreur lors de la sauvegarde:', err);
      alert('Erreur lors de la sauvegarde: ' + (err.message || 'Erreur inconnue'));
    }
  };

  const handleCancel = () => {
    if (hasChanges && !confirm('Abandonner les modifications ?')) {
      return;
    }
    onClose();
  };

  // Initialiser SortableJS pour le drag and drop
  useEffect(() => {
    const container = categoriesListRef.current;
    if (!container || typeof Sortable === 'undefined' || !onReorder) return;

    // Destroy previous instance
    if (sortableInstanceRef.current) {
      sortableInstanceRef.current.destroy();
    }

    sortableInstanceRef.current = new Sortable(container, {
      handle: '.category-drag-handle',
      animation: 150,
      delay: 150,
      delayOnTouchOnly: true,
      ghostClass: 'sortable-ghost',
      chosenClass: 'sortable-chosen',
      dragClass: 'sortable-drag',
      onEnd: (evt) => {
        if (evt.oldIndex === evt.newIndex) return;

        // Mettre à jour l'ordre local seulement
        setLocalCategories(prev => {
          const newCats = [...prev];
          const [moved] = newCats.splice(evt.oldIndex, 1);
          newCats.splice(evt.newIndex, 0, moved);
          return newCats;
        });
        setHasChanges(true);
      }
    });

    return () => {
      if (sortableInstanceRef.current) {
        sortableInstanceRef.current.destroy();
        sortableInstanceRef.current = null;
      }
    };
  }, [localCategories]);
  
  return (
    <div className="modal-overlay" onClick={handleCancel}>
      <div className="modal fade-in" onClick={(e) => e.stopPropagation()} style={{ maxWidth: '500px' }}>
        <div className="modal-header">Gérer les catégories</div>

        <div className="category-list" ref={categoriesListRef} style={{ maxHeight: '400px', overflowY: 'auto' }}>
          {localCategories.map(cat => (
            <div key={cat.id} className="category-item" data-id={cat.id}>
              {onReorder && (
                <div className="drag-handle category-drag-handle" aria-label="Glisser pour réordonner">
                  <Icon name="grip" size={20} />
                </div>
              )}
              <div
                className="category-color-preview"
                style={{ background: editingId === cat.id ? editColor : cat.color }}
              ></div>

              {editingId === cat.id ? (
                <div style={{ flex: 1 }}>
                  <input
                    type="text"
                    className="form-input"
                    value={editName}
                    onChange={(e) => setEditName(e.target.value)}
                    style={{ marginBottom: '8px' }}
                  />
                  <div className="color-picker-grid">
                    {colorOptions.map(color => (
                      <div
                        key={color}
                        className={`color-option ${editColor === color ? 'selected' : ''}`}
                        style={{ background: color }}
                        onClick={() => setEditColor(color)}
                      ></div>
                    ))}
                  </div>
                </div>
              ) : (
                <div className="category-item-info">
                  <div className={`category-item-name ${cat.hidden ? 'category-item-hidden' : ''}`}>{cat.name}</div>
                </div>
              )}

              <div style={{ display: 'flex', gap: '8px' }}>
                {editingId === cat.id ? (
                  <>
                    <button className="action-btn" onClick={() => handleSave(cat.id)} aria-label="Valider"><Icon name="check" size={18} /></button>
                    <button className="action-btn" onClick={() => setEditingId(null)} aria-label="Annuler"><Icon name="close" size={18} /></button>
                  </>
                ) : (
                  <>
                    <button
                      className="action-btn"
                      onClick={() => handleToggleHidden(cat.id)}
                      title={cat.hidden ? 'Réafficher la catégorie' : 'Masquer la catégorie'}
                    >
                      <Icon name={cat.hidden ? 'eye' : 'eyeOff'} size={18} />
                    </button>
                    <button className="action-btn" onClick={() => handleEdit(cat)} aria-label="Modifier"><Icon name="edit" size={18} /></button>
                    {cat.name !== 'Autre' && (
                      <button className="action-btn delete" onClick={() => handleDelete(cat.id)} aria-label="Supprimer"><Icon name="trash" size={18} /></button>
                    )}
                  </>
                )}
              </div>
            </div>
          ))}
        </div>
        
        {showAddForm ? (
          <form onSubmit={handleCreate} className="category-form-preview">
            <div className="form-group">
              <label className="form-label">Nom de la catégorie</label>
              <input
                type="text"
                className="form-input"
                value={newName}
                onChange={(e) => setNewName(e.target.value)}
                placeholder="Cardio"
                required
                autoFocus
              />
            </div>
            <div className="form-group">
              <label className="form-label">Couleur</label>
              <div className="color-picker-grid">
                {colorOptions.map(color => (
                  <div
                    key={color}
                    className={`color-option ${newColor === color ? 'selected' : ''}`}
                    style={{ background: color }}
                    onClick={() => setNewColor(color)}
                  ></div>
                ))}
              </div>
            </div>
            <div className="form-actions">
              <button type="button" className="btn btn-secondary" onClick={() => setShowAddForm(false)}>
                Annuler
              </button>
              <button type="submit" className="btn btn-primary">
                Créer
              </button>
            </div>
          </form>
        ) : (
          <button 
            className="btn btn-primary" 
            style={{ width: '100%', justifyContent: 'center', marginTop: '20px' }}
            onClick={() => setShowAddForm(true)}
          >
            + Nouvelle catégorie
          </button>
        )}

        <div style={{ display: 'flex', gap: '12px', marginTop: '20px', position: 'relative', zIndex: 10 }}>
          <button
            type="button"
            className="btn btn-secondary"
            style={{ flex: 1, justifyContent: 'center', position: 'relative', zIndex: 10 }}
            onClick={handleCancel}
          >
            Annuler
          </button>
          <button
            type="button"
            className="btn btn-primary"
            style={{ flex: 1, justifyContent: 'center', position: 'relative', zIndex: 10 }}
            onClick={handleSaveAll}
          >
            Enregistrer
          </button>
        </div>
      </div>
    </div>
  );
}

// Stats Modal
function QuickStatsModal({ stats, user, onClose }) {
  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal fade-in" onClick={(e) => e.stopPropagation()}>
        <div className="modal-header">Statistiques globales</div>
        
        <div style={{ marginTop: '20px', display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '12px' }}>
          <div className="stat-card">
            <div className="stat-value">{stats.total_exercises || 0}</div>
            <div className="stat-label">Exercices différents</div>
          </div>
          <div className="stat-card">
            <div className="stat-value">{stats.total_performances || 0}</div>
            <div className="stat-label">Performances totales</div>
          </div>
          <div className="stat-card">
            <div className="stat-value">{stats.total_tonnage?.toFixed(0) || 0}</div>
            <div className="stat-label">Tonnage total (kg)</div>
          </div>
          {stats.avg_session_duration > 0 ? (
            <div className="stat-card">
              <div className="stat-value">
                {Math.floor(stats.avg_session_duration / 60)}:{String(Math.floor(stats.avg_session_duration % 60)).padStart(2, '0')}
              </div>
              <div className="stat-label">Durée moy. séance</div>
            </div>
          ) : stats.total_sessions > 0 ? (
            <div className="stat-card">
              <div className="stat-value">{stats.total_sessions}</div>
              <div className="stat-label">Séances complétées</div>
            </div>
          ) : null}
          {stats.total_sessions > 0 && stats.avg_session_duration > 0 && (
            <div className="stat-card">
              <div className="stat-value">{stats.total_sessions}</div>
              <div className="stat-label">Séances complétées</div>
            </div>
          )}
          {stats.first_workout && (
            <div className="stat-card">
              <div className="stat-value" style={{ fontSize: '16px' }}>
                {formatDate(stats.first_workout, 'short', user?.timezone || 'Europe/Paris')}
              </div>
              <div className="stat-label">Premier entraînement</div>
            </div>
          )}
        </div>

        <button 
          className="btn btn-primary" 
          style={{ width: '100%', justifyContent: 'center', marginTop: '24px' }}
          onClick={onClose}
        >
          Fermer
        </button>
      </div>
    </div>
  );
}

// Main App
function FitnessApp() {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(() => {
    // Initialiser loading à true seulement si un token existe
    return !!localStorage.getItem('token');
  });
  const [exercises, setExercises] = useState([]);
  const [performances, setPerformances] = useState([]);
  const [categories, setCategories] = useState([]);
  const [calendarDays, setCalendarDays] = useState([]);
  const [view, setView] = useState('dashboard');
  const [progressViewKey, setProgressViewKey] = useState(0);
  const [showAddModal, setShowAddModal] = useState(false);
  const [selectedExercise, setSelectedExercise] = useState(null);
  const [editingPerformance, setEditingPerformance] = useState(null);
  const [showAddExercise, setShowAddExercise] = useState(false);
  const [editingExercise, setEditingExercise] = useState(null);
  const [showStatsModal, setShowStatsModal] = useState(false);
  const [showCategoryManager, setShowCategoryManager] = useState(false);
  const [showProfileModal, setShowProfileModal] = useState(false);
  const [globalStats, setGlobalStats] = useState(null);
  const [showPWAPrompt, setShowPWAPrompt] = useState(false);
  const [logger, setLogger] = useState(null); // saisie plein écran : { exerciseId, category }
  const [newExerciseCategory, setNewExerciseCategory] = useState(null); // catégorie affichée quand on ajoute un exercice

  const handleViewChange = (newView) => {
    if (newView === 'progress' && view === 'progress') {
      // Reset progress view when clicking on already active progress nav
      setProgressViewKey(prev => prev + 1);
    }
    setView(newView);
  };

  const handleLogin = (userData) => {
    setUser(userData);

    // Vérifier si la popup ne doit pas être affichée
    const dismissed = localStorage.getItem('pwa-install-dismissed');

    // Vérifier si on est sur mobile
    const userAgent = navigator.userAgent || navigator.vendor || window.opera;
    const isMobile = /iPad|iPhone|iPod|android/i.test(userAgent);

    // Afficher la popup si: pas déjà dismiss + sur mobile
    if (!dismissed && isMobile) {
      setTimeout(() => {
        setShowPWAPrompt(true);
      }, 1000); // Petite pause pour une meilleure UX
    }
  };

  useEffect(() => {
    const token = localStorage.getItem('token');
    const userData = localStorage.getItem('user');
    if (token && userData) {
      try {
        setUser(JSON.parse(userData));
      } catch (e) {
        localStorage.removeItem('user');
        localStorage.removeItem('token');
        setLoading(false);
        return;
      }
      // Rafraîchir les données utilisateur depuis le serveur
      api.getProfile().then(freshUserData => {
        localStorage.setItem('user', JSON.stringify(freshUserData));
        setUser(freshUserData);
      }).catch(err => {
        console.error('Failed to refresh user data:', err);
      });
    } else {
      // Pas d'utilisateur connecté, afficher l'écran de connexion
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    if (user) {
      loadData();
    }
  }, [user]);

  useEffect(() => {
    if (user && user.dark_mode === 1) {
      document.body.classList.add('dark-mode');
    } else {
      document.body.classList.remove('dark-mode');
    }
  }, [user]);

  const loadData = async (quiet = false) => {
    try {
      if (quiet !== true) setLoading(true);
      const [exs, perfs, cats, cal, stats] = await Promise.all([
        api.getExercises(),
        api.getPerformances(),
        api.getCategories(),
        api.request(`/api/calendar?month=${new Date().getMonth() + 1}&year=${new Date().getFullYear()}`),
        api.getStats()
      ]);

      setExercises(exs);
      setPerformances(perfs);
      setCategories(cats);
      setCalendarDays(cal);
      setGlobalStats(stats);
    } catch (err) {
      console.error('Load error:', err);
    } finally {
      setLoading(false);
    }
  };

  // Ajoute une série à la perf du jour de l'exercice, ou la crée si c'est la première du jour.
  // Le jour se lit dans le fuseau de l'utilisateur (avant : date UTC, qui coupait une séance après minuit).
  const addSetToToday = async (exercise, value, weight, notes) => {
    const tz = user?.timezone || 'Europe/Paris';
    const today = getTodayInTimezone(tz);
    const exerciseId = exercise.id;
    const todayPerf = performances.find(p =>
      p.exercise_id === exerciseId &&
      p.performed_at && dayOf(p.performed_at, tz) === today
    );

    if (todayPerf) {
      // Une perf existe déjà aujourd'hui pour cet exercice -> ajouter comme série
      let existingSeries = [];
      try {
        existingSeries = todayPerf.series ? JSON.parse(todayPerf.series) : [];
      } catch (e) {
        existingSeries = [];
      }

      // Si pas encore de séries, convertir la perf existante en première série
      if (existingSeries.length === 0) {
        existingSeries = [{ reps: todayPerf.value, weight: todayPerf.weight }];
      }

      const newSeries = [...existingSeries, { reps: value, weight: weight }];
      await api.updatePerformance(todayPerf.id, {
        notes: notes || todayPerf.notes,
        series: newSeries
      });

      // Mettre à jour le sessionExercises si session active
      if (localStorage.getItem('sessionActive') === 'true') {
        const sessionExercises = JSON.parse(localStorage.getItem('sessionExercises') || '{}');
        sessionExercises[exerciseId] = todayPerf.id;
        localStorage.setItem('sessionExercises', JSON.stringify(sessionExercises));
      }
    } else {
      // Première perf du jour pour cet exercice
      const newPerf = await api.createPerformance({
        exercise_id: exerciseId,
        value, weight, notes,
        series: [{ reps: value, weight: weight }]
      });

      if (localStorage.getItem('sessionActive') === 'true') {
        const sessionExercises = JSON.parse(localStorage.getItem('sessionExercises') || '{}');
        sessionExercises[exerciseId] = newPerf.id;
        localStorage.setItem('sessionExercises', JSON.stringify(sessionExercises));
      }
    }
  };

  // Un exercice « reps » s'ouvre dans la saisie plein écran, les autres unités gardent la modale
  const openExercise = (exercise, category = null) => {
    if (exercise.unit === 'reps') {
      setLogger({ exerciseId: exercise.id, category });
    } else {
      setSelectedExercise(exercise);
      setEditingPerformance(null);
      setShowAddModal(true);
    }
  };

  const logout = () => {
    localStorage.removeItem('token');
    localStorage.removeItem('user');
    setUser(null);
  };

  const updateCalendarDay = async (date, status) => {
    try {
      await api.request('/api/calendar', {
        method: 'POST',
        body: JSON.stringify({ date, status })
      });
      const cal = await api.request(`/api/calendar?month=${new Date(date).getMonth() + 1}&year=${new Date(date).getFullYear()}`);
      setCalendarDays(cal);
    } catch (err) {
      console.error('Calendar update error:', err);
    }
  };

  const loggerExercise = logger ? exercises.find(ex => ex.id === logger.exerciseId) : null;
  const loggerGroup = logger && logger.category ? (groupByCategory(exercises, categories)[logger.category] || []) : [];
  const loggerIndex = loggerExercise ? loggerGroup.findIndex(ex => ex.id === loggerExercise.id) : -1;
  const nextExercise = loggerIndex >= 0 ? loggerGroup[loggerIndex + 1] || null : null;

  if (!user) {
    return <LoginScreen onLogin={handleLogin} />;
  }

  if (loading) {
    return (
      <div style={{
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        height: '100vh',
        flexDirection: 'column',
        gap: '16px'
      }}>
        <div style={{
          width: '48px',
          height: '48px',
          border: '4px solid #e5e7eb',
          borderTop: '4px solid #3b82f6',
          borderRadius: '50%',
          animation: 'spin 1s linear infinite'
        }}></div>
        <div style={{ fontSize: '14px', color: '#6b7280' }}>Chargement...</div>
      </div>
    );
  }

  return (
    <div>
      <PWAInstallPrompt
        show={showPWAPrompt}
        onClose={() => setShowPWAPrompt(false)}
      />

      <ConnectionIndicator />
      
      {view === 'dashboard' && (
        <SessionHome
          user={user}
          exercises={exercises}
          performances={performances}
          categories={categories}
          onOpenExercise={openExercise}
          onToggleHidden={async (id, hidden) => {
            try {
              await api.setExerciseHidden(id, hidden);
              await loadData();
            } catch (error) {
              console.error('Erreur lors du masquage de l\'exercice:', error);
              alert('Erreur lors du masquage de l\'exercice. Veuillez réessayer.');
            }
          }}
          onToggleCategoryHidden={async (id, hidden) => {
            try {
              await api.setCategoryHidden(id, hidden);
              await loadData();
            } catch (error) {
              console.error('Erreur lors du masquage de la catégorie:', error);
              alert('Erreur lors du masquage de la catégorie. Veuillez réessayer.');
            }
          }}
          onAddExercise={(categoryName) => {
            setEditingExercise(null);
            setNewExerciseCategory(categoryName || null);
            setShowAddExercise(true);
          }}
          onShowStats={() => setShowStatsModal(true)}
          onManageCategories={() => setShowCategoryManager(true)}
          onShowProfile={() => setShowProfileModal(true)}
        />
      )}

      {view === 'calendar' && (
        <CalendarView
          user={user}
          calendarDays={calendarDays}
          performances={performances}
          exercises={exercises}
          onUpdateDay={updateCalendarDay}
          onShowStats={() => setShowStatsModal(true)}
          onManageCategories={() => setShowCategoryManager(true)}
          onShowProfile={() => setShowProfileModal(true)}
        />
      )}

      {view === 'progress' && (
        <ProgressView
          key={progressViewKey}
          user={user}
          exercises={exercises}
          performances={performances}
          categories={categories}
          onLogSet={(exercise) => openExercise(exercise)}
          onEditPerformance={(perf, exercise) => {
            setSelectedExercise(exercise);
            setEditingPerformance(perf);
            setShowAddModal(true);
          }}
          onDeletePerformance={async (id) => {
            await api.deletePerformance(id);
            loadData();
          }}
          onShowStats={() => setShowStatsModal(true)}
          onManageCategories={() => setShowCategoryManager(true)}
          onShowProfile={() => setShowProfileModal(true)}
        />
      )}

      {view === 'weight' && (
        <WeightTrackerView
          user={user}
          onShowStats={() => setShowStatsModal(true)}
          onManageCategories={() => setShowCategoryManager(true)}
          onShowProfile={() => setShowProfileModal(true)}
        />
      )}

      <BottomNav currentView={view} onViewChange={handleViewChange} />
      
      {logger && loggerExercise && (
        <SetLogger
          key={loggerExercise.id}
          exercise={loggerExercise}
          user={user}
          performances={performances}
          nextExercise={nextExercise}
          onClose={() => setLogger(null)}
          onNext={() => setLogger({ ...logger, exerciseId: nextExercise.id })}
          onSaveSet={async (exercise, reps, weight) => {
            await addSetToToday(exercise, reps, weight, null);
            await loadData(true);
          }}
          onEditExercise={(exercise) => {
            setEditingExercise(exercise);
            setShowAddExercise(true);
          }}
          onToggleHidden={async (id, hidden) => {
            try {
              await api.setExerciseHidden(id, hidden);
              await loadData();
            } catch (error) {
              console.error('Erreur lors du masquage de l\'exercice:', error);
              alert('Erreur lors du masquage de l\'exercice. Veuillez réessayer.');
            }
          }}
          onDeleteExercise={async (id) => {
            try {
              await api.deleteExercise(id);
              await loadData();
            } catch (error) {
              console.error('Erreur lors de la suppression de l\'exercice:', error);
              alert('Erreur lors de la suppression de l\'exercice. Veuillez réessayer.');
            }
          }}
          onEditToday={(perf) => {
            setSelectedExercise(loggerExercise);
            setEditingPerformance(perf);
            setShowAddModal(true);
          }}
        />
      )}

      {showAddModal && selectedExercise && (
        <AddPerformanceModal
          exercise={selectedExercise}
          editingPerformance={editingPerformance}
          performances={performances}
          onClose={() => {
            setShowAddModal(false);
            setSelectedExercise(null);
            setEditingPerformance(null);
          }}
          onEditExercise={(exercise) => {
            setEditingExercise(exercise);
            setShowAddExercise(true);
          }}
          onSave={async (value, weight, notes, series) => {
            try {
              if (editingPerformance) {
                await api.updatePerformance(editingPerformance.id, { value, weight, notes, series });
              } else if (series) {
                // Mode séries explicites (édition d'une perf avec séries)
                await api.createPerformance({
                  exercise_id: selectedExercise.id,
                  value, weight, notes, series
                });
              } else {
                // Mode simple : ajoute une série à la perf du jour (ou la crée)
                await addSetToToday(selectedExercise, value, weight, notes);
              }
              await loadData();
              setShowAddModal(false);
              setSelectedExercise(null);
              setEditingPerformance(null);
            } catch (error) {
              console.error('Erreur lors de l\'enregistrement de la performance:', error);
              alert('Erreur lors de l\'enregistrement de la performance. Veuillez réessayer.');
            }
          }}
          onDeleteExercise={async (id) => {
            try {
              await api.deleteExercise(id);
              await loadData();
            } catch (error) {
              console.error('Erreur lors de la suppression de l\'exercice:', error);
              alert('Erreur lors de la suppression de l\'exercice. Veuillez réessayer.');
            }
          }}
          onToggleHidden={async (id, hidden) => {
            try {
              await api.setExerciseHidden(id, hidden);
              await loadData();
            } catch (error) {
              console.error('Erreur lors du masquage de l\'exercice:', error);
              alert('Erreur lors du masquage de l\'exercice. Veuillez réessayer.');
            }
          }}
          onDeletePerformance={async (id) => {
            try {
              await api.deletePerformance(id);
              await loadData();
            } catch (error) {
              console.error('Erreur lors de la suppression de la performance:', error);
              alert('Erreur lors de la suppression de la performance. Veuillez réessayer.');
            }
          }}
        />
      )}

      {showAddExercise && (
        <AddExerciseModal
          exercise={editingExercise}
          categories={categories}
          defaultCategoryId={editingExercise ? null : categories.find(c => c.name === newExerciseCategory)?.id}
          onClose={() => {
            setShowAddExercise(false);
            setEditingExercise(null);
          }}
          onSave={async (data) => {
            if (editingExercise) {
              await api.updateExercise(editingExercise.id, data);
            } else {
              await api.createExercise(data);
            }
            loadData();
            setShowAddExercise(false);
            setEditingExercise(null);
          }}
        />
      )}

      {showStatsModal && globalStats && (
        <QuickStatsModal
          stats={globalStats}
          user={user}
          onClose={() => setShowStatsModal(false)}
        />
      )}

      {showCategoryManager && (
        <CategoryManagerModal
          categories={categories}
          onClose={() => {
            setShowCategoryManager(false);
            loadData();
          }}
          onSave={async (id, data) => {
            await api.updateCategory(id, data);
            loadData();
          }}
          onDelete={async (id) => {
            await api.deleteCategory(id);
            loadData();
          }}
          onCreate={async (data) => {
            await api.createCategory(data);
            loadData();
          }}
          onReorder={async (order) => {
            await api.reorderCategories(order);
            loadData();
          }}
          onToggleHidden={async (id, hidden) => {
            await api.setCategoryHidden(id, hidden);
            loadData();
          }}
        />
      )}

      {showProfileModal && (
        <ProfileView
          user={user}
          onUpdateProfile={async (data) => {
            try {
              await api.updateProfile(data);
              // Récupérer les données fraîches depuis le serveur
              const freshUserData = await api.getProfile();
              localStorage.setItem('user', JSON.stringify(freshUserData));
              setUser(freshUserData);
            } catch (err) {
              console.error('Profile update error:', err);
              throw err;
            }
          }}
          onLogout={logout}
          onDeleteAccount={async () => {
            try {
              const result = await api.deleteAccount();
              console.log('Compte supprimé:', result);
              alert('Votre compte a été supprimé avec succès.');
              // Nettoyer le localStorage et recharger la page pour retourner à l'écran de connexion
              localStorage.removeItem('token');
              localStorage.removeItem('user');
              window.location.reload();
            } catch (err) {
              const userMessage = getErrorMessage(err.serverMessage || err.message, false);
              alert(userMessage);
              console.error('Delete account error:', err);
              throw err;
            }
          }}
          onClose={() => setShowProfileModal(false)}
        />
      )}
    </div>
  );
}

ReactDOM.render(<FitnessApp />, document.getElementById('root'));
