import 'dart:io';
import 'dart:typed_data';
import 'dart:ui';
import 'package:camera/camera.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:google_mlkit_face_detection/google_mlkit_face_detection.dart';

class MLVisionService {
  static final MLVisionService _instance = MLVisionService._internal();
  factory MLVisionService() => _instance;
  
  late FaceDetector _faceDetector;
  bool _isInitialized = false;

  MLVisionService._internal();

  void initialize() {
    if (_isInitialized) return;
    
    _faceDetector = FaceDetector(
      options: FaceDetectorOptions(
        performanceMode: FaceDetectorMode.accurate,
        enableLandmarks: true,
        enableContours: false,
        enableClassification: false,
        enableTracking: true,
        minFaceSize: 0.15,
      ),
    );
    _isInitialized = true;
  }

  Future<List<Face>> getFacesFromImage(
    CameraImage image,
    InputImageRotation rotation,
  ) async {
    if (!_isInitialized) throw Exception("MLVisionService not initialized");

    try {
      final inputImage = await _convertCameraImageToInputImage(image, rotation);
      return await _faceDetector.processImage(inputImage);
    } catch (e) {
      debugPrint('Error processing image: $e');
      return [];
    }
  }

  Future<List<Face>> getFacesFromFile(File file) async {
    if (!_isInitialized) throw Exception("MLVisionService not initialized");

    try {
      final inputImage = InputImage.fromFile(file);
      return await _faceDetector.processImage(inputImage);
    } catch (e) {
      // debugPrint('Error processing file: $e');
      return [];
    }
  }

  Future<InputImage> _convertCameraImageToInputImage(
    CameraImage image,
    InputImageRotation rotation,
  ) async {
    try {
      // Log format gambar untuk debugging
      debugPrint('Camera image format: ${image.format.raw}');

      // Coba konversi berdasarkan format
      switch (image.format.group) {
        case ImageFormatGroup.yuv420:
          return _handleYUV420(image, rotation);
        case ImageFormatGroup.bgra8888:
          return _handleBGRA8888(image, rotation);
        case ImageFormatGroup.jpeg:
          return _handleJPEG(image, rotation);
        case ImageFormatGroup.nv21:
          return _handleNV21(image, rotation);
        default:
          // Fallback ke NV21 untuk format yang tidak dikenal
          return _handleYUV420(image, rotation);
      }
    } catch (e) {
      debugPrint('Error in image conversion: $e');
      // Fallback ke metode alternatif jika konversi gagal
      return _fallbackConversion(image, rotation);
    }
  }

  InputImage _handleYUV420(CameraImage image, InputImageRotation rotation) {
    final bytes = _convertYUV420ToNV21(image);
    return InputImage.fromBytes(
      bytes: bytes,
      metadata: InputImageMetadata(
        size: Size(image.width.toDouble(), image.height.toDouble()),
        rotation: rotation,
        format: InputImageFormat.nv21,
        bytesPerRow: image.planes[0].bytesPerRow,
      ),
    );
  }

  InputImage _handleBGRA8888(CameraImage image, InputImageRotation rotation) {
    final bytes = _concatenatePlanes(image.planes);
    return InputImage.fromBytes(
      bytes: bytes,
      metadata: InputImageMetadata(
        size: Size(image.width.toDouble(), image.height.toDouble()),
        rotation: rotation,
        format: InputImageFormat.bgra8888,
        bytesPerRow: image.planes[0].bytesPerRow,
      ),
    );
  }

  InputImage _handleJPEG(CameraImage image, InputImageRotation rotation) {
    final bytes = _concatenatePlanes(image.planes);
    return InputImage.fromBytes(
      bytes: bytes,
      metadata: InputImageMetadata(
        size: Size(image.width.toDouble(), image.height.toDouble()),
        rotation: rotation,
        format: InputImageFormat.yv12,
        bytesPerRow: image.planes[0].bytesPerRow,
      ),
    );
  }

  InputImage _handleNV21(CameraImage image, InputImageRotation rotation) {
    final bytes = _concatenatePlanes(image.planes);
    return InputImage.fromBytes(
      bytes: bytes,
      metadata: InputImageMetadata(
        size: Size(image.width.toDouble(), image.height.toDouble()),
        rotation: rotation,
        format: InputImageFormat.nv21,
        bytesPerRow: image.planes[0].bytesPerRow,
      ),
    );
  }

  InputImage _fallbackConversion(CameraImage image, InputImageRotation rotation) {
    // Coba semua metode konversi yang mungkin
    try {
      return _handleYUV420(image, rotation);
    } catch (e) {
      debugPrint('YUV420 conversion failed: $e');
    }

    try {
      return _handleNV21(image, rotation);
    } catch (e) {
      debugPrint('NV21 conversion failed: $e');
    }

    // Jika semua gagal, gunakan concatenate planes sebagai upaya terakhir
    final bytes = _concatenatePlanes(image.planes);
    return InputImage.fromBytes(
      bytes: bytes,
      metadata: InputImageMetadata(
        size: Size(image.width.toDouble(), image.height.toDouble()),
        rotation: rotation,
        format: InputImageFormat.nv21, // Default ke NV21
        bytesPerRow: image.planes[0].bytesPerRow,
      ),
    );
  }

  Uint8List _convertYUV420ToNV21(CameraImage image) {
    final int width = image.width;
    final int height = image.height;
    
    // Y plane (Luminance)
    final yPlane = image.planes[0].bytes;
    
    // UV planes (Chrominance)
    final uvPlane = Uint8List(width * height ~/ 2);
    int uvIndex = 0;
    
    for (int row = 0; row < height / 2; row++) {
      for (int col = 0; col < width / 2; col++) {
        final int uvRow = row * image.planes[1].bytesPerRow;
        final int uvCol = col * image.planes[1].bytesPerPixel!;
        
        // V component
        uvPlane[uvIndex++] = image.planes[1].bytes[uvRow + uvCol];
        // U component
        uvPlane[uvIndex++] = image.planes[2].bytes[uvRow + uvCol];
      }
    }
    
    return Uint8List.fromList([...yPlane, ...uvPlane]);
  }

  Uint8List _concatenatePlanes(List<Plane> planes) {
    final WriteBuffer buffer = WriteBuffer();
    for (final plane in planes) {
      buffer.putUint8List(plane.bytes);
    }
    return buffer.done().buffer.asUint8List();
  }

  void dispose() {
    _faceDetector.close();
    _isInitialized = false;
  }
}