import 'dart:io';
import 'dart:math' as math;
import 'dart:async';
import 'dart:isolate';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:camera/camera.dart';

import 'package:provider/provider.dart';
import '../../core/constants/app_constants.dart';
import '../../core/theme/app_theme.dart';
import '../../data/models/target_type.dart';
import '../crop/crop_screen.dart';
import '../session/session_provider.dart';
import 'widgets/image_source_button.dart';
import '../../services/opencv_target_service.dart';
import '../../services/parallelism_service.dart'; // NOUVEAU
import 'package:image/image.dart' as img;
import 'package:path_provider/path_provider.dart';

/// Paramètres sérialisables pour convertir une frame caméra dans un Isolate.
class _FrameConvertParams {
  final int width;
  final int height;
  final Uint8List yBytes;
  final Uint8List uBytes;
  final Uint8List vBytes;
  final int yRowStride;
  final int uvRowStride;
  final int uvPixelStride;
  final String outputPath;

  _FrameConvertParams({
    required this.width,
    required this.height,
    required this.yBytes,
    required this.uBytes,
    required this.vBytes,
    required this.yRowStride,
    required this.uvRowStride,
    required this.uvPixelStride,
    required this.outputPath,
  });
}

/// Conversion YUV420 → RGB (sous-échantillonnée /2) + encodage JPEG, exécutée
/// dans un Isolate pour ne JAMAIS bloquer le thread UI pendant l'aperçu caméra
/// (c'était la cause principale du lag à la prise de photo). Retourne true si
/// le fichier d'analyse a bien été écrit.
bool _convertAndEncodeFrameIsolate(_FrameConvertParams p) {
  try {
    final int targetW = p.width ~/ 2;
    final int targetH = p.height ~/ 2;
    final image = img.Image(width: targetW, height: targetH);

    for (int y = 0; y < targetH; y++) {
      for (int x = 0; x < targetW; x++) {
        final int srcX = x * 2;
        final int srcY = y * 2;

        final int yIndex = srcY * p.yRowStride + srcX;
        final int uvIndex =
            (srcY ~/ 2) * p.uvRowStride + (srcX ~/ 2) * p.uvPixelStride;

        final int yVal = p.yBytes[yIndex];
        final int uVal = p.uBytes[uvIndex] - 128;
        final int vVal = p.vBytes[uvIndex] - 128;

        final int r = (yVal + 1.402 * vVal).clamp(0, 255).toInt();
        final int g = (yVal - 0.344136 * uVal - 0.714136 * vVal)
            .clamp(0, 255)
            .toInt();
        final int b = (yVal + 1.772 * uVal).clamp(0, 255).toInt();

        image.setPixelRgb(x, y, r, g, b);
      }
    }

    // PERF : qualité 50 pour la frame d'analyse temps réel (jetable).
    File(p.outputPath).writeAsBytesSync(img.encodeJpg(image, quality: 50));
    return true;
  } catch (_) {
    return false;
  }
}

class CaptureScreen extends StatefulWidget {
  const CaptureScreen({super.key});

  @override
  State<CaptureScreen> createState() => _CaptureScreenState();
}

class _CaptureScreenState extends State<CaptureScreen>
    with SingleTickerProviderStateMixin {
  final ImagePicker _picker = ImagePicker();
  final TargetType _selectedType = TargetType.concentric;
  final OpenCVTargetService _opencvService = OpenCVTargetService();

  // NOUVEAU : Service IMU de parallélisme
  // L'angle passe à l'ORANGE dès 3° d'inclinaison (pitch ou roll).
  // Petite hystérésis : il faut revenir sous 2° pour repasser au vert,
  // ce qui évite le clignotement quand la main tremble autour de 3°.
  final ParallelismService _parallelismService = ParallelismService(
    alignThreshold: 2.0,    // retour au vert : worstAngle ≤ 2°
    misalignThreshold: 3.0, // passage à l'orange : worstAngle > 3°
  );

  String? _selectedImagePath;
  bool _isLoading = false;
  bool _isCapturing = false; // garde anti-double-capture pendant le traitement
  // Chemin de la photo tout juste capturée, affichée FIGÉE pendant le
  // traitement (downscale). Tant qu'il est null, la capture physique est
  // encore en cours → l'utilisateur doit rester immobile. Une fois rempli,
  // la photo est prise et l'utilisateur peut bouger.
  String? _capturedPreviewPath;

  // Caméra live
  CameraController? _cameraController;
  List<CameraDescription>? _cameras;
  bool _showLiveCamera = false;

  // Animation pulsation du viseur
  late AnimationController _scanAnimationController;

  // Détection OpenCV (cible circulaire) — on garde le résultat COMPLET
  TargetDetectionResult? _targetResult; // NOUVEAU : centre + rayon de la cible
  bool _isAnalyzingFrame = false;

  // NOUVEAU : Données IMU en temps réel
  ParallelismData? _parallelismData;
  StreamSubscription<ParallelismData>? _parallelismSubscription;

  @override
  void initState() {
    super.initState();
    _scanAnimationController = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    )..repeat(reverse: true);
  }

  @override
  void dispose() {
    _cameraController?.dispose();
    _scanAnimationController.dispose();
    _parallelismSubscription?.cancel(); // NOUVEAU
    _parallelismService.dispose();      // NOUVEAU
    super.dispose();
  }

  // ─────────────────────────────────────────────────────────────────────────
  // NOUVEAU : Démarre l'écoute IMU et met à jour l'UI en temps réel
  // ─────────────────────────────────────────────────────────────────────────
  void _startParallelismDetection() {
    _parallelismSubscription?.cancel();
    _parallelismService.start();

    _parallelismSubscription = _parallelismService.stream.listen((data) {
      if (!mounted || !_showLiveCamera) return;

      // Le capteur émet à ~20 Hz, mais l'affichage ne dépend que du statut
      // (couleur verte/orange) et des angles arrondis à 0,1° (message). On ne
      // reconstruit donc QUE quand l'un d'eux change réellement → bien moins de
      // rebuilds inutiles de la vue caméra.
      final prev = _parallelismData;
      final bool changed = prev == null ||
          prev.status != data.status ||
          prev.pitchDegrees.toStringAsFixed(1) !=
              data.pitchDegrees.toStringAsFixed(1) ||
          prev.rollDegrees.toStringAsFixed(1) !=
              data.rollDegrees.toStringAsFixed(1);

      _parallelismData = data;
      if (changed) {
        setState(() {});
      }
    });
  }

  void _stopParallelismDetection() {
    _parallelismSubscription?.cancel();
    _parallelismSubscription = null;
    _parallelismService.stop();
    _parallelismData = null;
  }

  // ─────────────────────────────────────────────────────────────────────────
  // Calcule la couleur et le message depuis l'IMU + la détection de cible
  // ─────────────────────────────────────────────────────────────────────────

  /// La cible circulaire est-elle détectée et raisonnablement cadrée ?
  bool get _targetReady {
    final t = _targetResult;
    if (t == null || !t.success) return false;
    final bool centered =
        t.centerX > 0.15 && t.centerX < 0.85 && t.centerY > 0.15 && t.centerY < 0.85;
    final bool bigEnough = t.radius > 0.12;
    return centered && bigEnough;
  }

  /// Couleur du cadre : dépend UNIQUEMENT du parallélisme IMU.
  /// (La détection de cible sert seulement à dessiner le cercle, elle ne
  ///  bloque jamais le passage au vert.)
  Color get _frameColor {
    final bool imuAligned =
        _parallelismData == null || _parallelismData!.isAligned;
    return imuAligned ? const Color(0xFF00FF00) : Colors.orange;
  }

  /// Message d'aide selon le parallélisme IMU.
  String get _alignmentMessage {
    if (_parallelismData == null ||
        _parallelismData!.status == ParallelismStatus.unknown) {
      return 'ALIGNEZ LA CIBLE DANS LE CADRE';
    }

    // Aligné → message de validation (avec bonus si la cible est détectée)
    if (_parallelismData!.isAligned) {
      return _targetReady
          ? 'PARFAIT — CIBLE DÉTECTÉE, PRÊT'
          : 'PARALLÈLE OK — PRÊT À PHOTOGRAPHIER';
    }

    // Mal aligné → message directif selon l'axe le plus dévié
    final double pitch = _parallelismData!.pitchDegrees;
    final double roll = _parallelismData!.rollDegrees;

    if (pitch.abs() >= roll.abs()) {
      return pitch > 0
          ? 'INCLINEZ VERS L\'AVANT (${pitch.toStringAsFixed(1)}°)'
          : 'INCLINEZ VERS L\'ARRIÈRE (${(-pitch).toStringAsFixed(1)}°)';
    } else {
      return roll > 0
          ? 'INCLINEZ VERS LA GAUCHE (${roll.toStringAsFixed(1)}°)'
          : 'INCLINEZ VERS LA DROITE (${(-roll).toStringAsFixed(1)}°)';
    }
  }

  // ─────────────────────────────────────────────────────────────────────────
  // Initialisation de la caméra (inchangée, sauf ajout IMU)
  // ─────────────────────────────────────────────────────────────────────────
  Future<void> _initLiveCamera() async {
    final status = await Permission.camera.request();
    if (!status.isGranted) {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('Accès à l\'appareil photo requis')),
        );
      }
      return;
    }

    setState(() => _isLoading = true);
    try {
      if (_cameraController != null) {
        await _cameraController!.dispose();
        _cameraController = null;
      }

      _cameras = await availableCameras();
      if (_cameras != null && _cameras!.isNotEmpty) {
        // PERF : on capture en `veryHigh` (2160p) et non en `max`. Le pipeline
        // redimensionne ensuite tout à 1080 px max → la résolution `max`
        // (12–48 MP) ne servait à rien et rallongeait fortement le décodage
        // post-capture (« délai de quelques secondes »). `veryHigh` reste très
        // au-dessus du 1080 px final : aucune perte de qualité, mais décodage
        // bien plus rapide.
        _cameraController = CameraController(
          _cameras![0],
          ResolutionPreset.veryHigh,
          enableAudio: false,
          imageFormatGroup: ImageFormatGroup.jpeg,
        );

        await _cameraController!.initialize();

        setState(() {
          _showLiveCamera = true;
          _targetResult = null; // reset détection cible
          _parallelismData = null; // NOUVEAU : reset IMU
        });

        _startAlignmentDetection();
        _startParallelismDetection(); // NOUVEAU
      }
    } catch (e) {
      debugPrint('Erreur caméra: $e');
    } finally {
      setState(() => _isLoading = false);
    }
  }

  // ─────────────────────────────────────────────────────────────────────────
  // Détection OpenCV périodique (inchangée)
  // ─────────────────────────────────────────────────────────────────────────
  void _startAlignmentDetection() {
    DateTime? lastAnalysis;

    _cameraController!.startImageStream((CameraImage cameraImage) async {
      if (_isAnalyzingFrame) return;
      final now = DateTime.now();
      // Cadence ~1 s : assez réactif pour suivre la cible sans saturer le CPU.
      if (lastAnalysis != null &&
          now.difference(lastAnalysis!).inMilliseconds < 1000) return;
      lastAnalysis = now;
      _isAnalyzingFrame = true;

      try {
        final tempDir = await getTemporaryDirectory();
        final tempPath = '${tempDir.path}/frame_analysis.jpg';

        // On copie les octets des plans MAINTENANT (le buffer caméra est
        // recyclé dès la sortie du callback), puis on délègue TOUTE la
        // conversion YUV→RGB + l'encodage JPEG à un Isolate → l'aperçu et le
        // déclencheur restent fluides (plus de gel du thread UI).
        final params = _FrameConvertParams(
          width: cameraImage.width,
          height: cameraImage.height,
          yBytes: Uint8List.fromList(cameraImage.planes[0].bytes),
          uBytes: Uint8List.fromList(cameraImage.planes[1].bytes),
          vBytes: Uint8List.fromList(cameraImage.planes[2].bytes),
          yRowStride: cameraImage.planes[0].bytesPerRow,
          uvRowStride: cameraImage.planes[1].bytesPerRow,
          uvPixelStride: cameraImage.planes[1].bytesPerPixel ?? 1,
          outputPath: tempPath,
        );

        final bool ok =
            await Isolate.run(() => _convertAndEncodeFrameIsolate(params));
        if (!ok) {
          _isAnalyzingFrame = false;
          return;
        }

        final result = await _opencvService.detectTarget(tempPath);

        try {
          await File(tempPath).delete();
        } catch (_) {}

        if (mounted && _showLiveCamera) {
          setState(() {
            // On garde le résultat complet pour dessiner le cercle.
            _targetResult = result.success ? result : null;
          });
        }
      } catch (e) {
        debugPrint('Erreur analyse frame: $e');
      } finally {
        _isAnalyzingFrame = false;
      }
    });
  }

  void _stopAlignmentDetection() {
    try {
      if (_cameraController != null &&
          _cameraController!.value.isStreamingImages) {
        _cameraController!.stopImageStream();
      }
    } catch (_) {}
    _targetResult = null;
  }

  // ─────────────────────────────────────────────────────────────────────────
  // Galerie (inchangée)
  // ─────────────────────────────────────────────────────────────────────────
  Future<void> _handleGallerySelection() async {
    PermissionStatus status;

    if (Platform.isAndroid) {
      final deviceInfo = DeviceInfoPlugin();
      final androidInfo = await deviceInfo.androidInfo;
      status = androidInfo.version.sdkInt >= 33
          ? await Permission.photos.request()
          : await Permission.storage.request();
    } else {
      status = await Permission.photos.request();
    }

    if (status.isGranted) {
      _captureImageFromGallery();
    } else if (status.isPermanentlyDenied) {
      _showSettingsDialog();
    } else {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(
            content: Text('Accès à la galerie requis pour continuer'),
          ),
        );
      }
    }
  }

  void _showSettingsDialog() {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('Permission requise'),
        content: const Text(
          'L\'accès aux photos est nécessaire. Veuillez l\'activer dans les paramètres.',
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('Annuler'),
          ),
          TextButton(
            onPressed: () => openAppSettings(),
            child: const Text('Paramètres'),
          ),
        ],
      ),
    );
  }

  // ─────────────────────────────────────────────────────────────────────────
  // Build principal
  // ─────────────────────────────────────────────────────────────────────────
  @override
  Widget build(BuildContext context) {
    final sessionProvider = context.watch<SessionProvider>();
    final title = sessionProvider.isSessionActive
        ? 'Cible ${sessionProvider.targetCount + 1}'
        : 'Source';

    if (_showLiveCamera &&
        _cameraController != null &&
        _cameraController!.value.isInitialized) {
      return _buildLiveCameraView();
    }

    return Scaffold(
      appBar: AppBar(title: Text(title)),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(AppConstants.defaultPadding),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            const SizedBox(height: AppConstants.largePadding),
            _buildSectionTitle('Source de l\'Image'),
            const SizedBox(height: 12),
            Row(
              children: [
                Expanded(
                  child: ImageSourceButton(
                    icon: Icons.camera_alt,
                    label: 'Scanner',
                    onPressed: _isLoading ? null : _initLiveCamera,
                  ),
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: ImageSourceButton(
                    icon: Icons.photo_library,
                    label: 'Galerie',
                    onPressed: _isLoading ? null : _handleGallerySelection,
                  ),
                ),
              ],
            ),
            const SizedBox(height: AppConstants.largePadding),
            if (_isLoading)
              const Center(
                child: Padding(
                  padding: EdgeInsets.all(32),
                  child: CircularProgressIndicator(),
                ),
              )
            else
              _buildGuide(),
          ],
        ),
      ),
    );
  }

  // ─────────────────────────────────────────────────────────────────────────
  // Vue caméra live — MODIFIÉE pour utiliser les données IMU
  // ─────────────────────────────────────────────────────────────────────────
  Widget _buildLiveCameraView() {
    // Priorité : IMU (parallélisme physique) prime sur OpenCV (centrage)
    // Si l'IMU dit "mal aligné" → orange, sinon → vert
    final Color frameColor = _frameColor;
    final String alignmentMessage = _alignmentMessage;

    return Scaffold(
      backgroundColor: Colors.black,
      body: Stack(
        fit: StackFit.expand,
        children: [
          // 1. Flux vidéo caméra
          // On utilise le ratio RÉEL du capteur (et non un 3/4 codé en dur) :
          // sinon l'aperçu est étiré dès que la résolution choisie n'est pas
          // en 4:3 (ex. veryHigh en 16:9 écrasait la cible en rectangle).
          // `value.aspectRatio` est le ratio paysage du flux → on l'inverse
          // pour l'affichage portrait.
          Center(
            child: AspectRatio(
              aspectRatio: 1 / _cameraController!.value.aspectRatio,
              child: CameraPreview(_cameraController!),
            ),
          ),

          // 2. Coins du cadre et mire centrale (sans le cadre rectangulaire)
          Center(
            child: SizedBox(
              width: MediaQuery.of(context).size.width * 0.85,
              height: MediaQuery.of(context).size.width * 0.85,
              child: Stack(
                children: [
                  _buildCameraCorner(TopLeft: true, color: frameColor),
                  _buildCameraCorner(TopRight: true, color: frameColor),
                  _buildCameraCorner(BottomLeft: true, color: frameColor),
                  _buildCameraCorner(BottomRight: true, color: frameColor),
                  Center(
                    child: Container(
                      width: 24,
                      height: 24,
                      decoration: BoxDecoration(
                        shape: BoxShape.circle,
                        border: Border.all(color: frameColor, width: 1.2),
                      ),
                      child: Stack(
                        children: [
                          Center(
                            child: Container(
                              width: 20,
                              height: 1.2,
                              color: frameColor,
                            ),
                          ),
                          Center(
                            child: Container(
                              width: 1.2,
                              height: 20,
                              color: frameColor,
                            ),
                          ),
                        ],
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),

          // 3. Bouton retour
          Positioned(
            top: 40,
            left: 20,
            child: CircleAvatar(
              backgroundColor: Colors.black54,
              child: IconButton(
                icon: const Icon(Icons.arrow_back, color: Colors.white),
                onPressed: () {
                  _stopAlignmentDetection();
                  _stopParallelismDetection(); // NOUVEAU
                  setState(() {
                    _showLiveCamera = false;
                  });
                },
              ),
            ),
          ),

          // 4. Message d'alignement principal (texte dynamique IMU)
          Positioned(
            top: 50,
            left: 0,
            right: 0,
            child: Center(
              child: Container(
                padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
                decoration: BoxDecoration(
                  color: Colors.black.withValues(alpha: 0.7),
                  borderRadius: BorderRadius.circular(20),
                ),
                child: Text(
                  alignmentMessage,
                  style: TextStyle(
                    color: frameColor,
                    fontWeight: FontWeight.bold,
                    fontSize: 12,
                    letterSpacing: 1.2,
                  ),
                ),
              ),
            ),
          ),

          // NOUVEAU : Indicateur visuel des angles en bas à gauche
          if (_parallelismData != null &&
              _parallelismData!.status != ParallelismStatus.unknown)
            Positioned(
              bottom: 140,
              left: 16,
              child: _buildAngleIndicator(_parallelismData!),
            ),

          // Indicateur d'analyse en cours
          if (_isAnalyzingFrame)
            Positioned(
              top: 45,
              right: 20,
              child: Container(
                width: 10,
                height: 10,
                decoration: const BoxDecoration(
                  color: Colors.white54,
                  shape: BoxShape.circle,
                ),
              ),
            ),

          // 5. Bouton déclencheur
          Positioned(
            bottom: 40,
            left: 0,
            right: 0,
            child: Center(
              child: GestureDetector(
                onTap: _isCapturing ? null : _takePictureManually,
                child: Container(
                  height: 80,
                  width: 80,
                  decoration: BoxDecoration(
                    shape: BoxShape.circle,
                    border: Border.all(color: Colors.white, width: 4),
                  ),
                  child: Center(
                    child: Container(
                      height: 64,
                      width: 64,
                      decoration: const BoxDecoration(
                        color: Colors.white,
                        shape: BoxShape.circle,
                      ),
                    ),
                  ),
                ),
              ),
            ),
          ),

          // 6. Overlay bloquant pendant la capture/traitement : empêche de
          // bouger l'UI ou de relancer une capture tant que la photo n'est pas
          // chargée (absorbe tous les gestes). Deux phases distinctes pour
          // lever la confusion « puis-je bouger ? » :
          //   • _capturedPreviewPath == null → capture physique en cours :
          //     voile sur l'aperçu vivant + « Ne bougez pas ».
          //   • _capturedPreviewPath != null → photo prise, traitement en
          //     cours : on affiche l'image FIGÉE (le flux vivant disparaît)
          //     + « Photo prise ✓ — vous pouvez bouger ».
          if (_isCapturing)
            Positioned.fill(
              child: AbsorbPointer(
                child: _capturedPreviewPath == null
                    ? _buildCapturingOverlay()
                    : _buildProcessingOverlay(_capturedPreviewPath!),
              ),
            ),
        ],
      ),
    );
  }

  // ─────────────────────────────────────────────────────────────────────────
  // Overlay PHASE 1 : capture physique en cours → l'utilisateur doit rester
  // immobile. Voile semi-transparent par-dessus l'aperçu vivant.
  // ─────────────────────────────────────────────────────────────────────────
  Widget _buildCapturingOverlay() {
    return Container(
      color: Colors.black.withValues(alpha: 0.45),
      child: const Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            CircularProgressIndicator(color: Colors.white),
            SizedBox(height: 16),
            Text(
              'NE BOUGEZ PAS — CAPTURE…',
              style: TextStyle(
                color: Colors.white,
                fontSize: 14,
                fontWeight: FontWeight.w700,
                letterSpacing: 1.2,
              ),
            ),
          ],
        ),
      ),
    );
  }

  // ─────────────────────────────────────────────────────────────────────────
  // Overlay PHASE 2 : photo capturée, traitement en cours. On affiche l'image
  // FIGÉE (l'aperçu vivant disparaît, plus d'ambiguïté) et on indique
  // clairement que l'utilisateur peut maintenant bouger.
  // ─────────────────────────────────────────────────────────────────────────
  Widget _buildProcessingOverlay(String imagePath) {
    return Stack(
      fit: StackFit.expand,
      children: [
        // Image figée de la photo prise → recouvre totalement le flux caméra
        Image.file(File(imagePath), fit: BoxFit.cover),
        Container(
          color: Colors.black.withValues(alpha: 0.55),
          child: Center(
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                const Icon(
                  Icons.check_circle,
                  color: Color(0xFF00FF00),
                  size: 52,
                ),
                const SizedBox(height: 14),
                const Text(
                  'PHOTO PRISE',
                  style: TextStyle(
                    color: Color(0xFF00FF00),
                    fontSize: 16,
                    fontWeight: FontWeight.w700,
                    letterSpacing: 1.2,
                  ),
                ),
                const SizedBox(height: 20),
                const SizedBox(
                  width: 22,
                  height: 22,
                  child: CircularProgressIndicator(
                    color: Colors.white,
                    strokeWidth: 2.5,
                  ),
                ),
                const SizedBox(height: 14),
                Text(
                  'Traitement… vous pouvez bouger',
                  style: TextStyle(
                    color: Colors.white.withValues(alpha: 0.85),
                    fontSize: 13,
                    fontWeight: FontWeight.w500,
                  ),
                ),
              ],
            ),
          ),
        ),
      ],
    );
  }

  // ─────────────────────────────────────────────────────────────────────────
  // NOUVEAU : Widget affichant les angles pitch/roll en temps réel
  // ─────────────────────────────────────────────────────────────────────────
  Widget _buildAngleIndicator(ParallelismData data) {
    final Color color = data.isAligned ? const Color(0xFF00FF00) : Colors.orange;

    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
      decoration: BoxDecoration(
        color: Colors.black.withValues(alpha: 0.6),
        borderRadius: BorderRadius.circular(10),
        border: Border.all(color: color.withValues(alpha: 0.5), width: 1),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        mainAxisSize: MainAxisSize.min,
        children: [
          _buildAngleRow(
            label: 'Pitch',
            value: data.pitchDegrees,
            color: color,
          ),
          const SizedBox(height: 2),
          _buildAngleRow(
            label: 'Roll ',
            value: data.rollDegrees,
            color: color,
          ),
        ],
      ),
    );
  }

  Widget _buildAngleRow({
    required String label,
    required double value,
    required Color color,
  }) {
    final String sign = value >= 0 ? '+' : '';
    return Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        Text(
          '$label: ',
          style: const TextStyle(
            color: Colors.white60,
            fontSize: 11,
            fontFamily: 'monospace',
          ),
        ),
        Text(
          '$sign${value.toStringAsFixed(1)}°',
          style: TextStyle(
            color: color,
            fontSize: 11,
            fontWeight: FontWeight.bold,
            fontFamily: 'monospace',
          ),
        ),
      ],
    );
  }

  // ─────────────────────────────────────────────────────────────────────────
  // Coins du cadre (inchangés)
  // ─────────────────────────────────────────────────────────────────────────
  Widget _buildCameraCorner({
    bool TopLeft = false,
    bool TopRight = false,
    bool BottomLeft = false,
    bool BottomRight = false,
    Color color = const Color(0xFF00FF00),
  }) {
    return Positioned(
      top: (TopLeft || TopRight) ? 10 : null,
      bottom: (BottomLeft || BottomRight) ? 10 : null,
      left: (TopLeft || BottomLeft) ? 10 : null,
      right: (TopRight || BottomRight) ? 10 : null,
      child: Container(
        width: 20,
        height: 20,
        decoration: BoxDecoration(
          border: Border(
            top: (TopLeft || TopRight)
                ? BorderSide(color: color, width: 4)
                : BorderSide.none,
            bottom: (BottomLeft || BottomRight)
                ? BorderSide(color: color, width: 4)
                : BorderSide.none,
            left: (TopLeft || BottomLeft)
                ? BorderSide(color: color, width: 4)
                : BorderSide.none,
            right: (TopRight || BottomRight)
                ? BorderSide(color: color, width: 4)
                : BorderSide.none,
          ),
        ),
      ),
    );
  }

  // ─────────────────────────────────────────────────────────────────────────
  // Capture manuelle (MODIFIÉE : ajout dégradation post-capture pour perf)
  // ─────────────────────────────────────────────────────────────────────────
  Future<void> _takePictureManually() async {
    if (_cameraController == null || !_cameraController!.value.isInitialized) {
      return;
    }

    // Garde anti-double-capture : tout appui supplémentaire pendant le
    // traitement est ignoré (évite les photos multiples / états incohérents).
    if (_isCapturing) return;

    setState(() {
      _isCapturing = true;
      _isLoading = true;
      _capturedPreviewPath = null; // phase « capture en cours »
    });
    try {
      // On stoppe le flux d'analyse (obligatoire avant takePicture) puis on
      // capture IMMÉDIATEMENT, sans étape intermédiaire. Le reset de zoom
      // inutile (aucun zoom possible sur cet écran) a été retiré pour que la
      // photo parte sans délai perceptible.
      _stopAlignmentDetection();
      _stopParallelismDetection();

      final XFile photo = await _cameraController!.takePicture();

      // La photo est PHYSIQUEMENT capturée : on gèle l'aperçu en affichant
      // l'image prise (le flux caméra « vivant » disparaît) et on bascule le
      // message sur « vous pouvez bouger ». Tout ce qui suit n'est que du
      // traitement et n'exige plus l'immobilité de l'utilisateur.
      if (mounted) {
        setState(() => _capturedPreviewPath = photo.path);
      }

      // Pas de redressement perspective automatique ici : il déformait l'image
      // (écrasement en carré + rotation de l'axe de l'ellipse) et gelait le
      // thread UI. Le recentrage ET la rotation se font manuellement à l'écran
      // de centrage suivant, qui est précis et non destructeur.
      String finalPath = photo.path;

      // PERF : on dégrade fortement la photo juste après la capture.
      // Une résolution réduite suffit pour la calibration et le plotting,
      // et accélère nettement le décodage à chaque écran (chargement plotting).
      try {
        finalPath = await _downscaleForPipeline(finalPath);
      } catch (e) {
        debugPrint('Downscale ignoré: $e');
      }

      if (!mounted) return;
      setState(() {
        _selectedImagePath = finalPath;
        _showLiveCamera = false;
      });

      _analyzeImage();
    } catch (e) {
      debugPrint('Erreur lors du clic photo: $e');
    } finally {
      if (mounted) {
        setState(() {
          _isLoading = false;
          _isCapturing = false;
          _capturedPreviewPath = null;
        });
      }
    }
  }

  /// Recadre la photo sur la zone du viseur PUIS réduit sa résolution, en un
  /// seul décodage dans un Isolate (thread UI fluide).
  ///
  /// 1) Recadrage viseur : carré centré à 85 % du petit côté de la photo. C'est
  ///    EXACTEMENT le cadre des 4 coins verts/orange affiché à la capture
  ///    (`SizedBox` de `0.85 * largeur écran`, centré, l'aperçu caméra remplissant
  ///    la largeur). → l'écran de centrage ne reçoit QUE ce qui était visé entre
  ///    les coins, sans le fond capturé autour : plus d'impression de « dézoom ».
  /// 2) Dégradation résolution : 1080 px de côté max + JPEG qualité 70, largement
  ///    suffisant pour la détection visuelle et bien plus rapide à recharger.
  ///
  /// Baisser `maxSide` (ex. 900) ou `quality` (ex. 60) accélère encore au prix
  /// d'un peu de finesse à l'écran.
  Future<String> _downscaleForPipeline(String sourcePath) async {
    // On résout le chemin de sortie sur le thread UI (path_provider a besoin
    // du canal de plateforme), puis tout le travail lourd (décodage, crop,
    // resize, réencodage JPEG) part dans un Isolate → aucun gel du thread UI.
    final tempDir = await getTemporaryDirectory();
    final outPath =
        '${tempDir.path}/downscaled_${DateTime.now().millisecondsSinceEpoch}.jpg';

    return Isolate.run(() {
      final bytes = File(sourcePath).readAsBytesSync();
      final decoded = img.decodeImage(bytes);
      if (decoded == null) return sourcePath;

      // 1) Recadrage sur la zone du viseur (carré centré à 85 % du petit côté).
      final int side = (math.min(decoded.width, decoded.height) * 0.85).round();
      final int cropX =
          ((decoded.width - side) / 2).round().clamp(0, decoded.width - 1);
      final int cropY =
          ((decoded.height - side) / 2).round().clamp(0, decoded.height - 1);
      img.Image result = img.copyCrop(
        decoded,
        x: cropX,
        y: cropY,
        width: math.min(side, decoded.width - cropX),
        height: math.min(side, decoded.height - cropY),
      );

      // 2) Dégradation résolution si nécessaire.
      const int maxSide = 1080;
      final int longest = math.max(result.width, result.height);
      if (longest > maxSide) {
        final double ratio = maxSide / longest;
        result = img.copyResize(
          result,
          width: (result.width * ratio).round(),
          height: (result.height * ratio).round(),
          interpolation: img.Interpolation.linear, // rapide
        );
      }

      File(outPath).writeAsBytesSync(img.encodeJpg(result, quality: 70));
      return outPath;
    });
  }

  // ─────────────────────────────────────────────────────────────────────────
  // Helpers UI (inchangés)
  // ─────────────────────────────────────────────────────────────────────────
  Widget _buildSectionTitle(String title) {
    return Text(
      title,
      style: Theme.of(context)
          .textTheme
          .titleMedium
          ?.copyWith(fontWeight: FontWeight.bold),
    );
  }

  Widget _buildGuide() {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(AppConstants.defaultPadding),
        child: Column(
          children: [
            Icon(Icons.help_outline, size: 48, color: Colors.grey[400]),
            const SizedBox(height: 12),
            Text(
              'Conseils pour une bonne analyse',
              style: Theme.of(context)
                  .textTheme
                  .titleSmall
                  ?.copyWith(fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 12),
            _buildGuideItem(
              Icons.crop_free,
              'Cadrez la cible entière dans l\'image',
            ),
            _buildGuideItem(Icons.wb_sunny, 'Utilisez un bon éclairage'),
            _buildGuideItem(Icons.straighten, 'Prenez la photo de face'),
            _buildGuideItem(Icons.blur_off, 'Évitez les images floues'),
            _buildGuideItem(
              Icons.cleaning_services,
              'Nettoyer votre objectif avec une chiffonnette',
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildGuideItem(IconData icon, String text) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 4),
      child: Row(
        children: [
          Icon(icon, size: 20, color: AppTheme.primaryColor),
          const SizedBox(width: 12),
          Expanded(child: Text(text)),
        ],
      ),
    );
  }

  Future<void> _captureImageFromGallery() async {
    setState(() => _isLoading = true);
    try {
      final XFile? image = await _picker.pickImage(
        source: ImageSource.gallery,
        // PERF : limite d'import plus agressive pour accélérer le pipeline
        maxWidth: 1280,
        maxHeight: 1280,
        imageQuality: 75,
      );
      if (image != null) {
        setState(() {
          _selectedImagePath = image.path;
        });
        _analyzeImage();
      }
    } catch (e) {
      debugPrint('Erreur galerie: $e');
    } finally {
      if (mounted) setState(() => _isLoading = false);
    }
  }

  void _analyzeImage() {
    if (_selectedImagePath == null) return;

    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (_) => CropScreen(
          imagePath: _selectedImagePath!,
          targetType: _selectedType,
        ),
      ),
    );
  }
}