/// Gestionnaire d'état pour l'analyse des cibles (ChangeNotifier).
///
/// Gère le workflow complet d'analyse : chargement d'image, gestion des
/// impacts placés manuellement, calcul des scores, analyse de groupement
/// et sauvegarde des sessions.
library;

import 'dart:io';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:uuid/uuid.dart';
import '../../data/models/target_analysis.dart';
import '../../data/models/shot.dart';
import '../../data/models/target_type.dart';
import '../../data/repositories/session_repository.dart';
import '../../services/score_calculator_service.dart';
import '../../services/grouping_analyzer_service.dart';
import '../../services/ai_export_service.dart';

enum AnalysisState { initial, loading, success, error }

class AnalysisProvider extends ChangeNotifier {
  final ScoreCalculatorService _scoreCalculatorService;
  final GroupingAnalyzerService _groupingAnalyzerService;
  final SessionRepository _sessionRepository;
  final Uuid _uuid = const Uuid();

  AnalysisProvider({
    required ScoreCalculatorService scoreCalculatorService,
    required GroupingAnalyzerService groupingAnalyzerService,
    required SessionRepository sessionRepository,
  })  : _scoreCalculatorService = scoreCalculatorService,
        _groupingAnalyzerService = groupingAnalyzerService,
        _sessionRepository = sessionRepository;

  AnalysisState _state = AnalysisState.initial;
  String? _errorMessage;
  String? _imagePath;
  TargetType? _targetType;

  // AJOUT PROTECTION DU PLOTTING : Stockage permanent de la rotation du Crop
  double _cropRotation = 0.0;

  // Target calibration
  double _targetCenterX = 0.5;
  double _targetCenterY = 0.5;
  double _targetRadius = 0.4;
  double _targetInnerRadius = 0.04;
  int _ringCount = 10;
  List<double>? _ringRadii; // Individual ring radii multipliers
  double _imageAspectRatio = 1.0; // width / height

  // Shots
  List<Shot> _shots = [];

  // Score results
  ScoreResult? _scoreResult;

  // Grouping results
  GroupingResult? _groupingResult;

  // Getters
  AnalysisState get state => _state;
  String? get errorMessage => _errorMessage;
  String? get imagePath => _imagePath;
  TargetType? get targetType => _targetType;
  double get cropRotation => _cropRotation; // Getter pour le Plotting
  double get targetCenterX => _targetCenterX;
  double get targetCenterY => _targetCenterY;
  double get targetRadius => _targetRadius;
  double get targetInnerRadius => _targetInnerRadius;
  int get ringCount => _ringCount;
  List<double>? get ringRadii =>
      _ringRadii != null ? List.unmodifiable(_ringRadii!) : null;
  double get imageAspectRatio => _imageAspectRatio;
  List<Shot> get shots => List.unmodifiable(_shots);
  ScoreResult? get scoreResult => _scoreResult;
  GroupingResult? get groupingResult => _groupingResult;

  int get totalScore => _scoreResult?.totalScore ?? 0;
  int get shotCount => _shots.length;

  /// Modifie et mémorise la rotation de l'image pour le Plotting
  void setCropRotation(double rotation) {
    _cropRotation = rotation;
    notifyListeners();
  }

  /// Charge l'image et initialise les paramètres de cible par défaut.
  /// Le placement des impacts et la calibration se font ensuite manuellement.
  Future<void> analyzeImage(
    String imagePath,
    TargetType targetType, {
    Offset? manualCenter,
  }) async {
    _state = AnalysisState.loading;
    _imagePath = imagePath;
    _targetType = targetType;
    _errorMessage = null;
    notifyListeners();

    try {
      // Load image to get dimensions
      final file = File(imagePath);
      final bytes = await file.readAsBytes();
      final codec = await ui.instantiateImageCodec(bytes);
      final frame = await codec.getNextFrame();
      _imageAspectRatio = frame.image.width / frame.image.height;
      frame.image.dispose();

      _targetCenterX = manualCenter?.dx ?? 0.5;
      _targetCenterY = manualCenter?.dy ?? 0.5;
      _targetRadius = 0.4;
      _targetInnerRadius = 0.04;

      _shots = [];

      _state = AnalysisState.success;
      notifyListeners();
    } catch (e) {
      _state = AnalysisState.error;
      _errorMessage = 'Erreur d\'analyse: $e';
      notifyListeners();
    }
  }

  /// Add a manual shot
  void addShot(double x, double y) {
    final score = _calculateShotScore(x, y);
    final shot = Shot(id: _uuid.v4(), x: x, y: y, score: score, analysisId: '');

    _shots.add(shot);
    _recalculateScores();
    _recalculateGrouping();
    notifyListeners();
  }

  /// Efface tous les impacts en un clic (bouton ↻ de l'écran Plotting).
  /// La calibration (centre, rayon, anneaux) n'est pas touchée.
  void clearShots() {
    _shots.clear();
    _recalculateScores();
    _recalculateGrouping();
    notifyListeners();
  }

  /// Remove a shot
  void removeShot(String shotId) {
    _shots.removeWhere((shot) => shot.id == shotId);
    _recalculateScores();
    _recalculateGrouping();
    notifyListeners();
  }

  /// Update a shot's score manually
  void updateShotScore(String shotId, int newScore) {
    final index = _shots.indexWhere((shot) => shot.id == shotId);
    if (index == -1) return;

    _shots[index] = _shots[index].copyWith(score: newScore);
    _recalculateScores();
    notifyListeners();
  }

  /// Adjust target position
  void adjustTargetPosition(
    double centerX,
    double centerY,
    double innerRadius,
    double radius, {
    int? ringCount,
    List<double>? ringRadii,
    double zoomScale = 1.0,
    Offset offset = Offset.zero,
  }) {
    _targetCenterX = (centerX - offset.dx) / zoomScale;
    _targetCenterY = (centerY - offset.dy) / zoomScale;
    _targetRadius = radius / zoomScale;
    _targetInnerRadius = innerRadius / zoomScale;

    if (ringCount != null) {
      _ringCount = ringCount;
    }

    // CORRECTION : On accepte désormais la valeur null pour pouvoir réinitialiser l'espacement !
    _ringRadii = ringRadii;

    // Recalculate all shot scores based on new target position
    _shots = _shots.map((shot) {
      final newScore = _calculateShotScore(shot.x, shot.y);
      return shot.copyWith(score: newScore);
    }).toList();

    _recalculateScores();
    notifyListeners();
  }

  int _calculateShotScore(double x, double y) {
    if (_targetType == TargetType.concentric) {
      return _scoreCalculatorService.calculateConcentricScore(
        shotX: x,
        shotY: y,
        targetCenterX: _targetCenterX,
        targetCenterY: _targetCenterY,
        targetRadius: _targetRadius,
        ringCount: _ringCount,
        imageAspectRatio: _imageAspectRatio,
        ringRadii: _ringRadii,
      );
    } else {
      return _scoreCalculatorService.calculateSilhouetteScore(
        shotX: x,
        shotY: y,
        targetCenterX: _targetCenterX,
        targetCenterY: _targetCenterY,
        targetWidth: _targetRadius * 0.8,
        targetHeight: _targetRadius * 2,
      );
    }
  }

  void _recalculateScores() {
    if (_targetType == null) return;

    _scoreResult = _scoreCalculatorService.calculateScores(
      shots: _shots,
      targetType: _targetType!,
      targetCenterX: _targetCenterX,
      targetCenterY: _targetCenterY,
      targetRadius: _targetRadius,
      ringCount: _ringCount,
      imageAspectRatio: _imageAspectRatio,
      ringRadii: _ringRadii,
    );
  }

  void _recalculateGrouping() {
    _groupingResult = _groupingAnalyzerService.analyzeGrouping(_shots);
  }

  /// Exporte l'image et le json vers le backend IA
  Future<bool> exportToAiBackend() async {
    if (_imagePath == null || _targetType == null) {
      _errorMessage = "Impossible d'export : image ou type de cible manquant.";
      notifyListeners();
      return false;
    }

    final service = AiExportService();

    _state = AnalysisState.loading;
    notifyListeners();

    final success = await service.exportData(
      imagePath: _imagePath!,
      sessionId: 'export',
      targetType: _targetType!,
      targetCenterX: _targetCenterX,
      targetCenterY: _targetCenterY,
      targetRadius: _targetRadius,
      shots: _shots,
    );

    _state = AnalysisState.success;
    if (!success) {
      _errorMessage = "Échec de l'export vers le serveur IA.";
    }
    notifyListeners();
    return success;
  }

  /// Save the session
  Future<TargetAnalysis> saveSession({
    String? notes,
    String? sessionId,
    String? weaponName,
    String? weaponId,
    int? distance,
    DateTime? date,
  }) async {
    if (_imagePath == null || _targetType == null) {
      throw Exception('Cannot save: missing image or target type');
    }

    final targetAnalysis = await _sessionRepository.prepareTargetAnalysis(
      imagePath: _imagePath!,
      sessionId: sessionId ?? 'standalone',
      targetType: _targetType!,
      shots: _shots,
      totalScore: totalScore,
      groupingDiameter: _groupingResult?.diameter,
      groupingCenterX: _groupingResult?.centerX,
      groupingCenterY: _groupingResult?.centerY,
      notes: notes,
      targetCenterX: _targetCenterX,
      targetCenterY: _targetCenterY,
      targetRadius: _targetRadius,
    );

    if (sessionId != null && sessionId != 'standalone') {
      final existingSession = await _sessionRepository.getSession(sessionId);
      if (existingSession != null) {
        await _sessionRepository.addAnalysisToSession(
          sessionId,
          targetAnalysis,
        );
      } else {
        // CORRECTION : Utilise la date injectée plutôt que DateTime.now()
        await _sessionRepository.createSession(
          id: sessionId,
          weapon: weaponName ?? 'Inconnue',
          weaponId: weaponId,
          maxShotsPerTarget: shotCount,
          analyses: [targetAnalysis],
          notes: notes,
          distance: distance ?? 25,
          date: date ?? DateTime.now(),
        );
      }
    } else {
      // CORRECTION : S'applique aussi au flux alternatif sans ID préalable
      await _sessionRepository.createSession(
        weapon: weaponName ?? 'Inconnue',
        weaponId: weaponId,
        maxShotsPerTarget: shotCount,
        analyses: [targetAnalysis],
        notes: notes,
        distance: distance ?? 25,
        date: date ?? DateTime.now(),
      );
    }

    notifyListeners();
    return targetAnalysis;
  }

  /// Reset the provider
  void reset() {
    _state = AnalysisState.initial;
    _errorMessage = null;
    _imagePath = null;
    _targetType = null;
    _cropRotation = 0.0;
    _targetCenterX = 0.5;
    _targetCenterY = 0.5;
    _targetRadius = 0.4;
    _targetInnerRadius = 0.04;
    _ringCount = 10;
    _ringRadii = null;
    _imageAspectRatio = 1.0;
    _shots = [];
    _scoreResult = null;
    _groupingResult = null;
    notifyListeners();
  }

  /// Met à jour la position d'un impact de tir en direct (Glisser-Déposer).
  void updateShotPosition(String shotId, double newX, double newY) {
    final index = _shots.indexWhere((s) => s.id == shotId);
    if (index != -1) {
      // 1. On calcule dynamiquement le nouveau score de la zone survolée
      final newScore = _calculateShotScore(newX, newY);

      // 2. On remplace l'ancien impact par le nouveau avec ses coordonnées et son score ajustés
      _shots[index] = _shots[index].copyWith(x: newX, y: newY, score: newScore);

      // 3. On applique les deux méthodes natives de recalcul de ton application
      _recalculateScores();
      _recalculateGrouping();

      // 4. On demande à l'écran de se redessiner en direct
      notifyListeners();
    }
  }
}
