/// Service de détection d'impacts utilisant OpenCV.
library;

import 'dart:math' as math;
import 'package:opencv_dart/opencv_dart.dart' as cv;

/// Paramètres de détection d'impacts OpenCV
class OpenCVDetectionSettings {
  /// Seuil Canny bas pour la détection de contours
  final double cannyThreshold1;

  /// Seuil Canny haut pour la détection de contours
  final double cannyThreshold2;

  /// Distance minimale entre les centres des cercles détectés
  final double minDist;

  /// Paramètre 1 de HoughCircles (seuil Canny interne)
  final double param1;

  /// Paramètre 2 de HoughCircles (seuil d'accumulation)
  final double param2;

  /// Rayon minimum des cercles en pixels
  final int minRadius;

  /// Rayon maximum des cercles en pixels
  final int maxRadius;

  /// Taille du flou gaussien (doit être impair)
  final int blurSize;

  /// Utiliser la détection de contours en plus de Hough
  final bool useContourDetection;

  /// Circularité minimale pour la détection par contours (0-1)
  final double minCircularity;

  /// Surface minimale des contours
  final double minContourArea;

  /// Surface maximale des contours
  final double maxContourArea;

  const OpenCVDetectionSettings({
    this.cannyThreshold1 = 50,
    this.cannyThreshold2 = 150,
    this.minDist = 20,
    this.param1 = 100,
    this.param2 = 30,
    this.minRadius = 5,
    this.maxRadius = 50,
    this.blurSize = 5,
    this.useContourDetection = true,
    this.minCircularity = 0.6,
    this.minContourArea = 50,
    this.maxContourArea = 5000,
  });
}

/// Résultat de détection d'impact
class OpenCVDetectedImpact {
  /// Position X normalisée (0-1)
  final double x;

  /// Position Y normalisée (0-1)
  final double y;

  /// Rayon en pixels
  final double radius;

  /// Score de confiance (0-1)
  final double confidence;

  /// Méthode de détection utilisée
  final String method;

  const OpenCVDetectedImpact({
    required this.x,
    required this.y,
    required this.radius,
    this.confidence = 1.0,
    this.method = 'unknown',
  });
}

/// Service de détection d'impacts utilisant OpenCV
class OpenCVImpactDetectionService {
  /// Détecte les impacts dans une image en utilisant OpenCV
  List<OpenCVDetectedImpact> detectImpacts(
    String imagePath, {
    OpenCVDetectionSettings settings = const OpenCVDetectionSettings(),
  }) {
    try {
      final img = cv.imread(imagePath, flags: cv.IMREAD_COLOR);
      if (img.isEmpty) return [];

      final gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY);

      // Apply blur to reduce noise
      final blurKSize = (settings.blurSize, settings.blurSize);
      final blurred = cv.gaussianBlur(gray, blurKSize, 2, sigmaY: 2);

      final List<OpenCVDetectedImpact> detectedImpacts = [];

      final circles = cv.HoughCircles(
        blurred,
        cv.HOUGH_GRADIENT,
        1,
        settings.minDist,
        param1: settings.param1,
        param2: settings.param2,
        minRadius: settings.minRadius,
        maxRadius: settings.maxRadius,
      );

      if (circles.rows > 0 && circles.cols > 0) {
        // Mat shape: (1, N, 3) usually for HoughCircles (CV_32FC3)
        // We use at<Vec3f> directly.

        for (int i = 0; i < circles.cols; i++) {
          final vec = circles.at<cv.Vec3f>(0, i);
          final x = vec.val1;
          final y = vec.val2;
          final r = vec.val3;

          detectedImpacts.add(
            OpenCVDetectedImpact(
              x: x / img.cols,
              y: y / img.rows,
              radius: r,
              confidence: 0.8,
              method: 'hough',
            ),
          );
        }
      }

      // 2. Contour Detection (if enabled)
      if (settings.useContourDetection) {
        // Canny edge detection
        final edges = cv.canny(
          blurred,
          settings.cannyThreshold1,
          settings.cannyThreshold2,
        );

        // Find contours
        final contoursResult = cv.findContours(
          edges,
          cv.RETR_EXTERNAL,
          cv.CHAIN_APPROX_SIMPLE,
        );

        final contours = contoursResult.$1;
        // hierarchy is $2

        for (int i = 0; i < contours.length; i++) {
          final contour = contours[i];

          // Filter by area
          final area = cv.contourArea(contour);
          if (area < settings.minContourArea ||
              area > settings.maxContourArea) {
            continue;
          }

          // Filter by circularity
          final perimeter = cv.arcLength(contour, true);
          if (perimeter == 0) continue;
          final circularity = 4 * math.pi * area / (perimeter * perimeter);

          if (circularity < settings.minCircularity) continue;

          // Get bounding circle
          final enclosingCircle = cv.minEnclosingCircle(contour);
          final center = enclosingCircle.$1;
          final radius = enclosingCircle.$2;

          // Avoid duplicates (simple distance check against Hough results)
          bool isDuplicate = false;
          for (final existing in detectedImpacts) {
            final dx = existing.x * img.cols - center.x;
            final dy = existing.y * img.rows - center.y;
            final dist = math.sqrt(dx * dx + dy * dy);
            if (dist < radius) {
              isDuplicate = true;
              break;
            }
          }

          if (!isDuplicate) {
            detectedImpacts.add(
              OpenCVDetectedImpact(
                x: center.x / img.cols,
                y: center.y / img.rows,
                radius: radius,
                confidence: circularity, // Use circularity as confidence
                method: 'contour',
              ),
            );
          }
        }
      }

      return detectedImpacts;
    } catch (e) {
      // print('OpenCV Error: $e');
      return [];
    }
  }

  /// Détecte les impacts en utilisant une image de référence
  List<OpenCVDetectedImpact> detectFromReferences(
    String imagePath,
    List<({double x, double y})> referencePoints, {
    double tolerance = 2.0,
  }) {
    // Basic implementation: use average color/brightness of reference points
    // This is a placeholder for a more complex template matching or feature matching

    // For now, we can just run the standard detection but filter results
    // based on properties of the reference points (e.g. size/radius if we had it).

    // Returning standard detection for now to enable the feature.
    return detectImpacts(imagePath);
  }
}
