import { useState, useEffect, useCallback } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { useIdentity } from '../hooks/useIdentity';
import { useErrorHandler } from '../hooks/useErrorHandler';
import { useLoginStateMachine } from '../hooks/useLoginStateMachine';
import { ErrorDisplay } from './ErrorDisplay';
import { LoginCollectShare } from './LoginCollectShare';
import { getStoredRelays } from '../utils/relay';
import { GraphResolver } from '../services/graphResolver';
import { LoginBuilder } from '../services/loginBuilder';
import { useChannel } from '../hooks/useChannel';
import { isInIframe, sendToChannel } from '../utils/iframeChannel';
import { isPairingSatisfied, getPairsForMember } from '../utils/pairing';
import {
  buildAllowedPubkeys,
  checkDependenciesSatisfied,
  collectProgress,
  hasRemoteSignatures,
} from '../utils/loginValidation';
import { publishMessageAndSigs } from '../utils/loginPublish';
import {
  buildPairToMembers,
  buildPubkeyToPair,
  runCollectLoop,
  COLLECT_POLL_MS,
  COLLECT_TIMEOUT_MS,
  type ProofSignature,
} from '../utils/collectSignatures';
import {
  verifyTimestamp,
  verifyMessageSignaturesStrict,
} from '../utils/verification';
import * as nonceStore from '../utils/nonceStore';
import type { LoginPath, LoginProof } from '../types/identity';
import type { MessageBase } from '../types/message';

export function LoginScreen(): JSX.Element {
  const navigate = useNavigate();
  const [searchParams] = useSearchParams();
  const { identity } = useIdentity();
  const { error, handleError, clearError } = useErrorHandler();
  const { sendLoginProof } = useChannel();
  const { state: loginState, dispatch } = useLoginStateMachine();
  const serviceUuid = searchParams.get('service') ?? '';
  const membreUuid = searchParams.get('membre') ?? '';
  const [loginPath, setLoginPath] = useState<LoginPath | null>(null);
  const [proof, setProof] = useState<LoginProof | null>(null);
  const [isBuilding, setIsBuilding] = useState(false);
  const [isPublishing, setIsPublishing] = useState(false);
  const [isCollecting, setIsCollecting] = useState(false);
  const [awaitingRemoteAccept, setAwaitingRemoteAccept] = useState(false);
  const [collectedMerged, setCollectedMerged] = useState<ProofSignature[] | null>(
    null,
  );
  const [collectedPublishStats, setCollectedPublishStats] = useState<{
    successCount: number;
    relaysCount: number;
    relayStatus?: Array<{ endpoint: string; ok: boolean; error?: string }>;
  } | null>(null);
  const [publishRelayStatus, setPublishRelayStatus] = useState<
    Array<{ endpoint: string; ok: boolean; error?: string }>
  >([]);
  const [collectProgressState, setCollectProgressState] = useState<{
    satisfied: number;
    required: number;
  } | null>(null);

  const graphResolver = useState(() => new GraphResolver())[0];
  const pairingSatisfied = isPairingSatisfied();
  const relayOk = getStoredRelays().length > 0;

  useEffect(() => {
    if (!isInIframe()) {
      return;
    }
    sendToChannel({
      type: 'pairing-relay-status',
      payload: {
        pairingSatisfied,
        relayOk,
      },
    });
  }, [pairingSatisfied, relayOk]);

  // Rediriger selon l'état de la machine à états. Si service+membre en URL (parent iframe),
  // ne pas rediriger vers /services; dispatcher E_SELECT_* pour aller en S_LOGIN_BUILD_PATH.
  useEffect(() => {
    if (loginState === 'S_LOGIN_SELECT_SERVICE') {
      if (serviceUuid !== '' && membreUuid !== '') {
        dispatch({ type: 'E_SELECT_SERVICE', serviceUuid });
        dispatch({ type: 'E_SELECT_MEMBER', membreUuid });
        return;
      }
      navigate('/services');
      return;
    }
    if (loginState === 'S_LOGIN_SELECT_MEMBER') {
      if (serviceUuid !== '') {
        navigate(`/select-member?service=${serviceUuid}`);
        return;
      }
      navigate('/services');
      return;
    }
  }, [loginState, serviceUuid, membreUuid, navigate, dispatch]);

  const handleBuildPath = useCallback(async (): Promise<void> => {
    if (serviceUuid === '' || membreUuid === '') {
      handleError('Service UUID et Membre UUID requis', 'MISSING_PARAMS');
      return;
    }

    setIsBuilding(true);
    clearError();
    dispatch({ type: 'E_SELECT_SERVICE', serviceUuid });
    dispatch({ type: 'E_SELECT_MEMBER', membreUuid });
    try {
      const path = graphResolver.resolveLoginPath(serviceUuid, membreUuid);
      if (path === null) {
        handleError('Impossible de résoudre le chemin de login', 'PATH_RESOLUTION_FAILED');
        dispatch({ type: 'E_PATH_INVALID' });
        return;
      }
      setLoginPath(path);
      if (path.statut === 'incomplet') {
        handleError('Chemin incomplet. Synchronisez d\'abord les données.', 'INCOMPLETE_PATH');
        dispatch({ type: 'E_PATH_INCOMPLETE' });
      } else {
        dispatch({ type: 'E_PATH_OK' });
        dispatch({ type: 'E_PAIRS_OK' });
      }
    } catch (err) {
      handleError(err, 'Erreur lors de la construction du chemin');
      dispatch({ type: 'E_PATH_INVALID' });
    } finally {
      setIsBuilding(false);
    }
  }, [serviceUuid, membreUuid, graphResolver, handleError, clearError, dispatch]);

  // Construire le chemin automatiquement si service et membre sont fournis
  useEffect(() => {
    if (
      serviceUuid !== '' &&
      membreUuid !== '' &&
      loginPath === null &&
      loginState === 'S_LOGIN_BUILD_PATH' &&
      identity !== null
    ) {
      void handleBuildPath();
    }
  }, [serviceUuid, membreUuid, loginPath, loginState, identity, handleBuildPath]);

  const handleBuildChallenge = async (): Promise<void> => {
    if (identity === null || loginPath === null) {
      handleError('Identité ou chemin de login manquant', 'MISSING_REQUIREMENTS');
      return;
    }

    if (loginPath.statut !== 'complet') {
      handleError('Le chemin de login doit être complet avant de construire le challenge', 'INCOMPLETE_PATH');
      return;
    }

    setIsBuilding(true);
    clearError();
    try {
      const relays = getStoredRelays().map((r) => r.endpoint);
      if (relays.length === 0) {
        handleError('Aucun relais configuré', 'NO_RELAYS');
        return;
      }

      const loginBuilder = new LoginBuilder(identity, relays);

      const challenge = await loginBuilder.buildChallenge(
        loginPath.service_uuid,
        loginPath.action_login_uuid,
        loginPath.action_login_uuid,
        loginPath.membre_uuid,
      );

      const localPairs = getPairsForMember(loginPath.membre_uuid).filter(
        (p) =>
          p.is_local &&
          loginPath.pairs_attendus.includes(p.uuid),
      );

      const pk = identity.privateKey;
      if (pk === undefined) {
        handleError('Clé privée indisponible. Déverrouillez l\'identité.', 'IDENTITY_LOCKED');
        return;
      }

      const signatures: Array<{
        signature: string;
        cle_publique: string;
        nonce: string;
        pair_uuid: string;
      }> = [];

      for (const pair of localPairs) {
        const sig = loginBuilder.signChallenge(challenge, pair.uuid, pk);
        signatures.push({
          signature: sig,
          cle_publique: identity.publicKey,
          nonce: challenge.nonce,
          pair_uuid: pair.uuid,
        });
      }

      if (signatures.length === 0) {
        handleError(
          'Aucun pair local pour ce membre. Signez sur ce device ou ajoutez un pair.',
          'NO_LOCAL_PAIRS',
        );
        return;
      }

      const newProof = await loginBuilder.buildProof(challenge, signatures);
      setProof(newProof);
      dispatch({ type: 'E_CHALLENGE_READY' });
      dispatch({ type: 'E_SIGNATURES_COMPLETE' });
    } catch (err) {
      handleError(err, 'Erreur lors de la construction du challenge');
    } finally {
      setIsBuilding(false);
    }
  };

  /**
   * Publish login proof to relays.
   * Order: message → signatures → keys (strict order as per specs).
   * Then collect remote signatures if needed (mFA with 2 devices).
   * Finally verify locally and send proof to parent if in iframe.
   */
  const handlePublish = async (): Promise<void> => {
    if (identity === null || proof === null) {
      handleError('Identité ou preuve manquante', 'MISSING_REQUIREMENTS');
      return;
    }

    // Anti-rejeu: vérifier timestamp dans la fenêtre
    if (!verifyTimestamp(proof.challenge.timestamp)) {
      handleError(
        'Timestamp hors fenêtre de validité (anti-rejeu)',
        'X_TIMESTAMP_OUT_OF_WINDOW',
      );
      return;
    }

    // Anti-rejeu: vérifier nonce unique
    await nonceStore.init();
    if (nonceStore.hasUsed(proof.challenge.nonce)) {
      handleError('Nonce déjà utilisé (anti-rejeu)', 'X_NONCE_REUSED');
      return;
    }

    setIsPublishing(true);
    clearError();
    try {
      const relays = getStoredRelays().filter((r) => r.enabled);
      if (relays.length === 0) {
        handleError('Aucun relais activé', 'NO_ENABLED_RELAYS');
        return;
      }

      const loginBuilder = new LoginBuilder(identity, relays.map((r) => r.endpoint));
      const msgChiffre = loginBuilder.challengeToMsgChiffre(proof.challenge);
      const msgCle = loginBuilder.challengeToMsgCle(proof.challenge);

      // Publication en ordre strict : message → signatures → clés
      const publishResult = await publishMessageAndSigs(
        relays,
        msgChiffre,
        proof.signatures,
        msgCle,
      );
      setPublishRelayStatus(publishResult.relayStatus);
      if (publishResult.successCount === 0) {
        handleError('Échec de la publication sur tous les relais', 'PUBLISH_FAILED');
        return;
      }

      // Collecte des signatures distantes (mFA avec 2 devices)
      let merged = proof.signatures;
      if (loginPath !== null) {
        setIsCollecting(true);
        setCollectProgressState(null);
        try {
          const pairToMembers = buildPairToMembers(loginPath.pairs_attendus);
          const pubkeyToPair = buildPubkeyToPair(
            identity.publicKey,
            loginPath.pairs_attendus,
          );
          const endpoints = relays.map((r) => r.endpoint);
          // Boucle de collecte : fetch signatures par hash jusqu'à satisfaction ou timeout
          merged = await runCollectLoop({
            relayEndpoints: endpoints,
            hash: proof.challenge.hash,
            ourSigs: proof.signatures,
            path: loginPath,
            pairToMembers,
            pubkeyToPair,
            opts: {
              pollMs: COLLECT_POLL_MS,
              timeoutMs: COLLECT_TIMEOUT_MS,
              onProgress: (m) => {
                const p = collectProgress(loginPath, m, pairToMembers);
                setCollectProgressState({
                  satisfied: p.satisfied,
                  required: p.required,
                });
              },
            },
          });
        } finally {
          setIsCollecting(false);
          setCollectProgressState(null);
        }
      }

      // Si des signatures distantes ont été collectées, demander confirmation utilisateur
      if (
        loginPath !== null &&
        hasRemoteSignatures(merged, loginPath.pairs_attendus)
      ) {
        setCollectedMerged(merged);
        setCollectedPublishStats({
          successCount: publishResult.successCount,
          relaysCount: relays.length,
          relayStatus: publishResult.relayStatus,
        });
        setAwaitingRemoteAccept(true);
        setIsPublishing(false);
        return;
      }

      const finalProof = await loginBuilder.buildProof(proof.challenge, merged);

      // Vérification locale finale (avant envoi au parent)
      // Vérifie: hash, signatures (clés autorisées), dépendances, graphe, anti-rejeu
      const verificationResults: string[] = [];
      const verificationErrors: string[] = [];
      let verificationSuccess = true;

      // Vérifier hash
      verificationResults.push(`✓ Hash: ${proof.challenge.hash.slice(0, 16)}...`);

      // Vérifier signatures
      if (loginPath !== null) {
        const allowedPubkeys = buildAllowedPubkeys(loginPath, identity.publicKey);
        if (allowedPubkeys.size > 0) {
          const minimalMsg = {
            hash: { hash_value: proof.challenge.hash },
          } as unknown as MessageBase;
          const sigs = merged.map((s) => ({
            hash: proof.challenge.hash,
            cle_publique: s.cle_publique,
            signature: s.signature,
            nonce: s.nonce,
          }));
          const { valid, unauthorized } = verifyMessageSignaturesStrict(
            minimalMsg,
            sigs,
            allowedPubkeys,
          );
          if (valid.length > 0) {
            verificationResults.push(`✓ Signatures valides: ${valid.length}`);
          } else {
            verificationErrors.push('Aucune signature valide');
            verificationSuccess = false;
          }
          if (unauthorized.length > 0) {
            verificationErrors.push(
              `Signatures non autorisées: ${unauthorized.length} (clés: ${unauthorized.map((s) => s.cle_publique.slice(0, 16)).join(', ')}...)`,
            );
            verificationSuccess = false;
          }
        } else {
          verificationErrors.push('Aucune clé publique autorisée trouvée dans les validateurs');
          verificationSuccess = false;
        }

        // Vérifier dépendances
        if (!checkDependenciesSatisfied(loginPath, finalProof)) {
          verificationErrors.push('Dépendances entre signatures non satisfaites (membres requis manquants)');
          verificationSuccess = false;
        }

        // Vérifier graphe
        if (loginPath.statut === 'complet') {
          verificationResults.push('✓ Graphe complet');
        } else {
          verificationErrors.push('Graphe incomplet - objets manquants dans le cache');
          verificationSuccess = false;
        }
      }

      // Vérifier anti-rejeu
      verificationResults.push('✓ Nonce unique');
      verificationResults.push('✓ Timestamp dans la fenêtre');

      // Si vérification échoue, arrêter ici
      if (!verificationSuccess) {
        // Log structuré pour le débogage
        const verificationLog = {
          timestamp: new Date().toISOString(),
          hash: proof.challenge.hash,
          nonce: proof.challenge.nonce,
          serviceUuid: loginPath?.service_uuid,
          membreUuid: loginPath?.membre_uuid,
          results: verificationResults,
          errors: verificationErrors,
          signaturesCount: merged.length,
          loginPathStatus: loginPath?.statut,
        };
        console.error('[Login] Verification failed:', verificationLog);
        handleError(
          `Vérification locale échouée: ${verificationErrors.join('; ')}`,
          'VERIFICATION_FAILED',
        );
        dispatch({ type: 'E_LOCAL_VERDICT_REJECT' });
        return;
      }

      // Log structuré pour le succès
      console.warn('[Login] Verification succeeded:', {
        timestamp: new Date().toISOString(),
        hash: proof.challenge.hash.slice(0, 16) + '...',
        serviceUuid: loginPath?.service_uuid,
        membreUuid: loginPath?.membre_uuid,
        signaturesCount: merged.length,
      });

      await nonceStore.markUsed(proof.challenge.nonce, proof.challenge.timestamp);

      const updatedProof = { ...finalProof, statut: 'publie' as const };
      setProof(updatedProof);
      sendLoginProof(updatedProof);

      if (publishResult.successCount < relays.length) {
        handleError(
          `Publication partielle: ${publishResult.successCount}/${relays.length} relais`,
          'PARTIAL_PUBLISH',
        );
        dispatch({ type: 'E_PUBLISH_LOGIN_PARTIAL' });
      } else {
        dispatch({ type: 'E_PUBLISH_LOGIN_OK' });
        dispatch({ type: 'E_LOCAL_VERDICT_ACCEPT' });
      }
    } catch (err) {
      handleError(err, 'Erreur lors de la publication');
    } finally {
      setIsPublishing(false);
    }
  };

  if (identity === null) {
    return (
      <main>
        <p>Identité requise pour se connecter</p>
        <button type="button" onClick={(): void => navigate('/')}>
          Retour
        </button>
      </main>
    );
  }

  if (!isPairingSatisfied()) {
    return (
      <main>
        <h1>Se connecter</h1>
        <p>Pairing obligatoire (G_PAIRING_SATISFIED). Configurez au moins un pair.</p>
        <div>
          <button
            type="button"
            onClick={(): void => navigate('/manage-pairs')}
          >
            Configurer le pairing
          </button>
          <button type="button" onClick={(): void => navigate('/')}>
            Retour
          </button>
        </div>
      </main>
    );
  }

  const handleBack = (): void => {
    dispatch({ type: 'E_BACK' });
    navigate('/');
  };

  const handleSyncNow = (): void => {
    dispatch({ type: 'E_SYNC_NOW' });
    navigate('/sync');
  };

  const handleAddPair = (): void => {
    dispatch({ type: 'E_ADD_PAIR' });
    navigate('/manage-pairs');
  };

  const handleRefuseRemote = (): void => {
    dispatch({ type: 'E_LOCAL_VERDICT_REJECT' });
    setAwaitingRemoteAccept(false);
    setCollectedMerged(null);
    setCollectedPublishStats(null);
  };

  const handleAcceptRemote = async (): Promise<void> => {
    if (
      identity === null ||
      proof === null ||
      loginPath === null ||
      collectedMerged === null ||
      collectedPublishStats === null
    ) {
      return;
    }
    clearError();
    const relays = getStoredRelays().filter((r) => r.enabled);
    const loginBuilder = new LoginBuilder(
      identity,
      relays.map((r) => r.endpoint),
    );
    const finalProof = await loginBuilder.buildProof(
      proof.challenge,
      collectedMerged,
    );
    if (!checkDependenciesSatisfied(loginPath, finalProof)) {
      handleError(
        'Dépendances entre signatures non satisfaites (membres requis manquants)',
        'DEPENDENCIES_UNSATISFIED',
      );
      return;
    }
    const allowedPubkeys = buildAllowedPubkeys(loginPath, identity.publicKey);
    if (allowedPubkeys.size > 0) {
      const minimalMsg = {
        hash: { hash_value: proof.challenge.hash },
      } as unknown as MessageBase;
      const sigs = collectedMerged.map((s) => ({
        hash: proof.challenge.hash,
        cle_publique: s.cle_publique,
        signature: s.signature,
        nonce: s.nonce,
      }));
      const { valid, unauthorized } = verifyMessageSignaturesStrict(
        minimalMsg,
        sigs,
        allowedPubkeys,
      );
      if (unauthorized.length > 0 || valid.length === 0) {
        handleError(
          'Signature(s) avec clé non autorisée par les validateurs (X_PUBKEY_NOT_AUTHORIZED)',
          'X_PUBKEY_NOT_AUTHORIZED',
        );
        return;
      }
    }
    await nonceStore.markUsed(
      proof.challenge.nonce,
      proof.challenge.timestamp,
    );
    const updatedProof = { ...finalProof, statut: 'publie' as const };
    setProof(updatedProof);
    sendLoginProof(updatedProof);
    if (
      collectedPublishStats.successCount < collectedPublishStats.relaysCount
    ) {
      handleError(
        `Publication partielle: ${collectedPublishStats.successCount}/${collectedPublishStats.relaysCount} relais`,
        'PARTIAL_PUBLISH',
      );
      dispatch({ type: 'E_PUBLISH_LOGIN_PARTIAL' });
    } else {
      dispatch({ type: 'E_PUBLISH_LOGIN_OK' });
      dispatch({ type: 'E_LOCAL_VERDICT_ACCEPT' });
    }
    setAwaitingRemoteAccept(false);
    setCollectedMerged(null);
    setCollectedPublishStats(null);
  };

  const showRecoveryActions =
    (loginPath !== null && loginPath.statut === 'incomplet') ||
    loginState === 'S_ERROR_RECOVERABLE';

  const handleRetry = (): void => {
    dispatch({ type: 'E_RETRY' });
    if (serviceUuid !== '' && membreUuid !== '') {
      void handleBuildPath();
    }
  };

  return (
    <main>
      <h1>Se connecter</h1>
      <p role="status" aria-live="polite" className="sr-only">
        État: {loginState}
      </p>
      {error !== null && <ErrorDisplay error={error} onDismiss={clearError} />}
      {(loginState === 'S_LOGIN_SELECT_SERVICE' ||
        loginState === 'S_LOGIN_SELECT_MEMBER') && (
        <section aria-labelledby="redirect-info">
          <p>Redirection vers la sélection...</p>
        </section>
      )}
      {loginPath !== null && (
        <section aria-labelledby="login-path">
          <h2 id="login-path">Chemin de login</h2>
          <div>
            <p>
              <strong>Statut:</strong> {loginPath.statut}
            </p>
            {loginPath.contrat_version !== undefined && (
              <p>
                <strong>Version contrat:</strong> {loginPath.contrat_version}
              </p>
            )}
            <div>
              <h3>Résumé du chemin</h3>
              <ul>
                <li>
                  <strong>Service:</strong> {loginPath.service_uuid}
                </li>
                {loginPath.contrat_uuid.length > 0 && (
                  <li>
                    <strong>Contrat(s):</strong>{' '}
                    {loginPath.contrat_uuid.join(', ')}
                  </li>
                )}
                {loginPath.champ_uuid !== undefined &&
                  loginPath.champ_uuid.length > 0 && (
                    <li>
                      <strong>Champ(s):</strong>{' '}
                      {loginPath.champ_uuid.join(', ')}
                    </li>
                  )}
                <li>
                  <strong>Action login:</strong> {loginPath.action_login_uuid}
                </li>
                <li>
                  <strong>Membre:</strong> {loginPath.membre_uuid}
                </li>
                <li>
                  <strong>Pairs attendus:</strong> {loginPath.pairs_attendus.length}
                </li>
              </ul>
            </div>
            {loginPath.signatures_requises.length > 0 && (
              <div>
                <h3>Signatures requises</h3>
                <table>
                  <thead>
                    <tr>
                      <th>Membre UUID</th>
                      <th>Pair UUID</th>
                      <th>Clé publique</th>
                      <th>Cardinalité min.</th>
                      <th>Dépendances</th>
                      <th>Statut</th>
                    </tr>
                  </thead>
                  <tbody>
                    {loginPath.signatures_requises.map((req, idx) => {
                      // Déterminer le statut de cette signature
                      const proofSigs = proof?.signatures ?? [];
                      const collectedSigs = collectedMerged ?? [];
                      const allSigs = [...proofSigs, ...collectedSigs];
                      const hasSignature = req.cle_publique !== undefined &&
                        allSigs.some((s) => s.cle_publique === req.cle_publique);
                      const status = hasSignature ? 'reçue' : 'manquante';

                      return (
                        <tr key={idx}>
                          <td>{req.membre_uuid.slice(0, 8)}...</td>
                          <td>
                            {req.pair_uuid !== undefined
                              ? `${req.pair_uuid.slice(0, 8)}...`
                              : '—'}
                          </td>
                          <td>
                            {req.cle_publique !== undefined
                              ? `${req.cle_publique.slice(0, 16)}...`
                              : '—'}
                          </td>
                          <td>
                            {req.cardinalite_minimale ?? '1'}
                          </td>
                          <td>
                            {req.dependances !== undefined &&
                            req.dependances.length > 0
                              ? req.dependances.join(', ')
                              : '—'}
                          </td>
                          <td>
                            {status === 'reçue' ? '✓ Reçue' : '✗ Manquante'}
                          </td>
                        </tr>
                      );
                    })}
                  </tbody>
                </table>
              </div>
            )}
            {loginPath.statut === 'complet' && (
              <button
                type="button"
                onClick={handleBuildChallenge}
                disabled={isBuilding}
              >
                {isBuilding ? 'Construction...' : 'Démarrer le login'}
              </button>
            )}
            {loginPath.statut === 'incomplet' && showRecoveryActions && (
              <div>
                <button type="button" onClick={handleSyncNow}>
                  Synchroniser
                </button>
                <button type="button" onClick={handleAddPair}>
                  Ajouter un pair
                </button>
              </div>
            )}
          </div>
        </section>
      )}
      {loginState === 'S_ERROR_RECOVERABLE' && (
        <section aria-labelledby="error-recoverable">
          <h2 id="error-recoverable">Erreur récupérable</h2>
          <p>
            Une erreur s&apos;est produite, mais elle peut être corrigée. Choisissez
            une action :
          </p>
          <div>
            <button type="button" onClick={handleRetry}>
              Réessayer
            </button>
            <button type="button" onClick={handleSyncNow}>
              Synchroniser maintenant
            </button>
            <button type="button" onClick={handleAddPair}>
              Ajouter un pair
            </button>
            <button
              type="button"
              onClick={(): void => {
                dispatch({ type: 'E_OPEN_DIAGNOSTIC' });
                navigate('/diagnostic');
              }}
            >
              Diagnostic avancé
            </button>
            <button
              type="button"
              onClick={(): void => {
                dispatch({ type: 'E_BACK' });
                navigate('/');
              }}
            >
              Retour à l&apos;accueil
            </button>
          </div>
        </section>
      )}

      {loginState === 'S_ERROR_FATAL' && (
        <section aria-labelledby="error-fatal">
          <h2 id="error-fatal">Erreur fatale</h2>
          <p>
            Une erreur bloquante s&apos;est produite. Impossible de continuer sans
            intervention externe.
          </p>
          {error !== null && (
            <div>
              <p>
                <strong>Erreur:</strong> {error.message}
              </p>
              {error.code !== undefined && (
                <p>
                  <strong>Code:</strong> {error.code}
                </p>
              )}
            </div>
          )}
          <div>
            <button
              type="button"
              onClick={(): void => {
                dispatch({ type: 'E_EXIT' });
                navigate('/');
              }}
            >
              Quitter
            </button>
          </div>
        </section>
      )}

      {showRecoveryActions && loginPath === null && loginState !== 'S_ERROR_RECOVERABLE' && (
        <section aria-label="Reprise">
          <button type="button" onClick={handleSyncNow}>
            Synchroniser
          </button>
          <button type="button" onClick={handleAddPair}>
            Ajouter un pair
          </button>
        </section>
      )}
      {proof !== null && (
        <section aria-labelledby="login-proof">
          <h2 id="login-proof">Message de login à valider</h2>
          <div>
            <div>
              <h3>Résumé public</h3>
              <ul>
                <li>
                  <strong>Service:</strong> {loginPath?.service_uuid ?? 'N/A'}
                </li>
                <li>
                  <strong>Type:</strong> {proof.challenge.datajson_public.types_uuid.join(', ')}
                </li>
                <li>
                  <strong>Timestamp:</strong>{' '}
                  {new Date(proof.challenge.datajson_public.timestamp).toLocaleString()}
                </li>
                <li>
                  <strong>Relais:</strong> {proof.challenge.datajson_public.services_uuid.length}
                </li>
              </ul>
            </div>
            <div>
              <h3>Détails techniques (mode avancé)</h3>
              <p>
                <strong>Hash:</strong> {proof.challenge.hash}
              </p>
              <p>
                <strong>Nonce:</strong> {proof.challenge.nonce}
              </p>
              <p>
                <strong>Statut:</strong> {proof.statut}
              </p>
              <p>
                <strong>Signatures:</strong> {proof.signatures.length}
              </p>
            </div>
            {proof.statut === 'en_attente' && !awaitingRemoteAccept && (
              <div>
                <button
                  type="button"
                  onClick={() => {
                    void handlePublish();
                  }}
                  disabled={isPublishing}
                >
                  {isCollecting
                    ? 'En attente des signatures des autres appareils…'
                    : isPublishing
                      ? 'Publication…'
                      : 'Publier la preuve'}
                </button>
                {publishRelayStatus.length > 0 && (
                  <div style={{ marginTop: '1rem' }}>
                    <h4>Statut par relais</h4>
                    <table>
                      <thead>
                        <tr>
                          <th>Relais</th>
                          <th>Statut</th>
                          <th>Erreur</th>
                        </tr>
                      </thead>
                      <tbody>
                        {publishRelayStatus.map((status, idx) => (
                          <tr key={idx}>
                            <td>{status.endpoint}</td>
                            <td>{status.ok ? '✓ OK' : '✗ Échec'}</td>
                            <td>{status.error ?? '—'}</td>
                          </tr>
                        ))}
                      </tbody>
                    </table>
                  </div>
                )}
              </div>
            )}
            {isCollecting && proof !== null && (
              <LoginCollectShare
                proof={proof}
                loginPath={loginPath}
                collectProgress={collectProgressState}
                collectedSignatures={collectedMerged ?? undefined}
                onRefresh={() => {
                  void (async (): Promise<void> => {
                    if (loginPath === null || proof === null) {
                      return;
                    }
                    setIsCollecting(true);
                    try {
                      const relays = getStoredRelays().filter((r) => r.enabled);
                      const endpoints = relays.map((r) => r.endpoint);
                      const pairToMembers = buildPairToMembers(loginPath.pairs_attendus);
                      const pubkeyToPair = buildPubkeyToPair(
                        identity?.publicKey ?? '',
                        loginPath.pairs_attendus,
                      );
                      const merged = await runCollectLoop({
                        relayEndpoints: endpoints,
                        hash: proof.challenge.hash,
                        ourSigs: proof.signatures,
                        path: loginPath,
                        pairToMembers,
                        pubkeyToPair,
                        opts: {
                          pollMs: COLLECT_POLL_MS,
                          timeoutMs: COLLECT_TIMEOUT_MS,
                          onProgress: (m) => {
                            const p = collectProgress(loginPath, m, pairToMembers);
                            setCollectProgressState({
                              satisfied: p.satisfied,
                              required: p.required,
                            });
                          },
                        },
                      });
                      setCollectedMerged(merged);
                    } finally {
                      setIsCollecting(false);
                    }
                  })();
                }}
                onViewDetails={(requirement, pairUuid) => {
                  // Afficher les détails de la signature dans une alerte ou un modal
                  const detail = `Membre: ${requirement}\nPair: ${pairUuid ?? 'N/A'}\nHash: ${proof.challenge.hash.slice(0, 16)}...\nNonce: ${proof.challenge.nonce.slice(0, 16)}...`;
                  alert(detail);
                }}
              />
            )}
            {awaitingRemoteAccept &&
              collectedMerged !== null &&
              proof !== null &&
              loginPath !== null && (
                <section
                  aria-labelledby="confirm-remote-sigs"
                  style={{ marginTop: '1rem' }}
                >
                  <h3 id="confirm-remote-sigs">
                    Confirmer les signatures du 2ᵉ appareil
                  </h3>
                  <p>
                    Des signatures ont été reçues du 2ᵉ appareil. Les mots ont
                    pu être visibles à l&apos;écran et interceptés par une
                    tierce personne. Confirmer que c&apos;est bien vous qui avez
                    validé sur l&apos;autre appareil ?
                  </p>
                  <div>
                    <button
                      type="button"
                      onClick={(): void => void handleAcceptRemote()}
                    >
                      Accepter
                    </button>
                    <button
                      type="button"
                      onClick={handleRefuseRemote}
                    >
                      Refuser
                    </button>
                  </div>
                </section>
              )}
          </div>
        </section>
      )}
      {loginState === 'S_LOGIN_SUCCESS' && (
        <section aria-labelledby="login-success">
          <h2 id="login-success">Login réussi</h2>
          <p>La preuve de login a été acceptée et la session est ouverte.</p>
          <div>
            <button
              type="button"
              onClick={(): void => {
                dispatch({ type: 'E_DONE' });
                navigate('/');
              }}
            >
              Terminer
            </button>
          </div>
        </section>
      )}

      {loginState === 'S_LOGIN_FAILURE' && (
        <section aria-labelledby="login-failure">
          <h2 id="login-failure">Login échoué</h2>
          <p>La vérification locale a échoué. Diagnostics :</p>
          {error !== null && (
            <div>
              <p>
                <strong>Erreur:</strong> {error.message}
              </p>
              {error.code !== undefined && (
                <p>
                  <strong>Code:</strong> {error.code}
                </p>
              )}
            </div>
          )}
          <div>
            <h3>Actions possibles :</h3>
            <ul>
              <li>
                <strong>Signatures manquantes :</strong> Vérifiez que tous les pairs
                requis ont signé. Utilisez &quot;Rafraîchir&quot; dans la collecte de
                signatures.
              </li>
              <li>
                <strong>Objets manquants :</strong> Synchronisez les données pour
                récupérer les contrats, membres, pairs manquants.
              </li>
              <li>
                <strong>Anti-rejeu :</strong> Le nonce a peut-être été réutilisé. Un
                nouveau login générera un nouveau nonce.
              </li>
              <li>
                <strong>Clés non autorisées :</strong> Vérifiez que les clés publiques
                des signatures correspondent aux validateurs du contrat.
              </li>
            </ul>
          </div>
          <div>
            <button
              type="button"
              onClick={(): void => {
                dispatch({ type: 'E_RETRY' });
                if (serviceUuid !== '' && membreUuid !== '') {
                  void handleBuildPath();
                }
              }}
            >
              Réessayer
            </button>
            <button
              type="button"
              onClick={(): void => {
                dispatch({ type: 'E_RESYNC' });
                navigate('/sync');
              }}
            >
              Resync
            </button>
            <button
              type="button"
              onClick={(): void => {
                dispatch({ type: 'E_BACK' });
                navigate('/');
              }}
            >
              Retour à l&apos;accueil
            </button>
          </div>
        </section>
      )}

      <div>
        <button
          type="button"
          onClick={handleBack}
          disabled={awaitingRemoteAccept || loginState === 'S_LOGIN_SUCCESS' || loginState === 'S_LOGIN_FAILURE'}
        >
          Retour
        </button>
      </div>
    </main>
  );
}
