// import 'dart:io'; // Not needed for OCR service
import 'package:flutter/foundation.dart';
import 'package:google_mlkit_text_recognition/google_mlkit_text_recognition.dart';
import '../core/constants/app_constants.dart';

class OCRService {
  static final OCRService _instance = OCRService._internal();
  factory OCRService() => _instance;
  OCRService._internal();

  final TextRecognizer _textRecognizer = TextRecognizer();

  // Extract text from image
  Future<OCRResult> extractTextFromImage(String imagePath) async {
    try {
      final inputImage = InputImage.fromFilePath(imagePath);
      final recognizedText = await _textRecognizer.processImage(inputImage);
      
      if (recognizedText.text.isEmpty) {
        throw Exception('Tidak ada teks yang terdeteksi dalam gambar');
      }

      // Process and clean the extracted text
      final processedText = _processExtractedText(recognizedText);
      final menuItems = _parseMenuItems(processedText);
      
      return OCRResult(
        rawText: recognizedText.text,
        processedText: processedText,
        menuItems: menuItems,
        confidence: _calculateAverageConfidence(recognizedText),
        isSuccess: true,
      );
    } catch (e) {
      debugPrint('OCR extraction error: $e');
      return OCRResult(
        rawText: '',
        processedText: '',
        menuItems: [],
        confidence: 0.0,
        isSuccess: false,
        error: e.toString(),
      );
    }
  }

  // Process and clean extracted text by merging lines on the same Y axis
  String _processExtractedText(RecognizedText recognizedText) {
    List<TextLine> allLines = [];
    
    // Kumpulkan semua baris dari semua blok
    for (TextBlock block in recognizedText.blocks) {
      for (TextLine line in block.lines) {
        if (line.text.trim().isNotEmpty && _getLineConfidence(line) > AppConstants.ocrConfidenceThreshold) {
          allLines.add(line);
        }
      }
    }
    
    if (allLines.isEmpty) return '';

    // Urutkan semua baris dari atas ke bawah (berdasarkan koordinat Y / top)
    allLines.sort((a, b) => a.boundingBox.top.compareTo(b.boundingBox.top));
    
    List<String> mergedLines = [];
    List<TextLine> currentLineGroup = [allLines.first];
    
    for (int i = 1; i < allLines.length; i++) {
      final line = allLines[i];
      final prevLine = currentLineGroup.first; // Pakai patokan baris pertama di grup
      
      // Jika selisih Y (top) kurang dari 15 pixel, berarti mereka di baris yang sama (sejajar)
      if ((line.boundingBox.top - prevLine.boundingBox.top).abs() < 15) {
        currentLineGroup.add(line);
      } else {
        // Selesai satu baris, urutkan dari kiri ke kanan (berdasarkan X / left)
        currentLineGroup.sort((a, b) => a.boundingBox.left.compareTo(b.boundingBox.left));
        String merged = currentLineGroup.map((e) => e.text.trim()).join(' ');
        mergedLines.add(_cleanOCRText(merged));
        
        currentLineGroup = [line];
      }
    }
    
    // Tambahkan grup terakhir
    if (currentLineGroup.isNotEmpty) {
      currentLineGroup.sort((a, b) => a.boundingBox.left.compareTo(b.boundingBox.left));
      String merged = currentLineGroup.map((e) => e.text.trim()).join(' ');
      mergedLines.add(_cleanOCRText(merged));
    }
    
    return mergedLines.join('\n');
  }

  // Clean common OCR errors
  String _cleanOCRText(String text) {
    // Memperbaiki OCR yang salah membaca huruf menjadi angka (atau sebaliknya) pada KATA,
    // tapi hindari merusak harga. Kita hapus replace 0 ke O karena fatal merusak angka harga.
    text = text.replaceAll(RegExp(r'[|]'), 'I');

    
    // Remove excessive whitespace
    text = text.replaceAll(RegExp(r'\s+'), ' ');
    
    // Fix common price patterns
    text = text.replaceAll(RegExp(r'Rp\s*(\d)'), 'Rp \$1');
    text = text.replaceAll(RegExp(r'(\d)\s*k'), '\$1.000');
    
    return text.trim();
  }

  // Parse menu items from processed text
  List<MenuItemOCR> _parseMenuItems(String text) {
    List<MenuItemOCR> menuItems = [];
    List<String> lines = text.split('\n');
    
    for (int i = 0; i < lines.length; i++) {
      String line = lines[i].trim();
      
      // Skip empty lines
      if (line.isEmpty) continue;
      
      // Try to identify menu items with prices
      MenuItemOCR? menuItem = _parseMenuItemLine(line);
      if (menuItem != null) {
        // Look for description in next line if available
        if (i + 1 < lines.length) {
          String nextLine = lines[i + 1].trim();
          if (!_containsPrice(nextLine) && nextLine.length > 10) {
            menuItem = menuItem.copyWith(description: nextLine);
            i++; // Skip the description line in next iteration
          }
        }
        menuItems.add(menuItem);
      }
    }
    
    return menuItems;
  }

  // Parse individual menu item line
  MenuItemOCR? _parseMenuItemLine(String line) {
    // Pattern for menu items with prices (with or without Rp)
    // Matches: Rp 20.000, 20.000, 25.000, 20000, 25k, 25K
    final pricePattern = RegExp(
      r'(?:Rp\.?\s*)?(\d{1,3}(?:[.,]\d{3})+(?:[.,]\d{2})?|\d{4,7}|\d{1,3}\s*[kK])\b', 
      caseSensitive: false
    );
    final match = pricePattern.firstMatch(line);
    
    if (match != null) {
      String priceStr = match.group(1)!;
      double price = _parsePrice(priceStr);
      
      // Extract menu name (text before price)
      String name = line.substring(0, match.start).trim();
      name = name.replaceAll(RegExp(r'[^\w\s]$'), ''); // Remove trailing punctuation
      
      if (name.isNotEmpty && name.length > 2) {
        return MenuItemOCR(
          name: name,
          price: price,
          description: '',
          category: _guessCategory(name),
        );
      }
    }
    
    return null;
  }

  // Parse price string to double
  double _parsePrice(String priceStr) {
    // Remove dots and commas, then parse
    String cleanPrice = priceStr.replaceAll(RegExp(r'[.,]'), '');
    
    // Handle 'k' suffix (thousands)
    if (priceStr.toLowerCase().contains('k')) {
      cleanPrice = cleanPrice.replaceAll(RegExp(r'k', caseSensitive: false), '000');
    }
    
    return double.tryParse(cleanPrice) ?? 0.0;
  }

  // Check if line contains price
  bool _containsPrice(String line) {
    return RegExp(
      r'(?:Rp\.?\s*)?(?:\d{1,3}(?:[.,]\d{3})+|\d{4,7}|\d{1,3}\s*[kK])\b', 
      caseSensitive: false
    ).hasMatch(line);
  }

  // Guess category based on menu name
  String _guessCategory(String name) {
    String lowerName = name.toLowerCase();
    
    if (lowerName.contains('kopi') || lowerName.contains('coffee') || 
        lowerName.contains('espresso') || lowerName.contains('latte') ||
        lowerName.contains('cappuccino') || lowerName.contains('americano')) {
      return 'Kopi';
    } else if (lowerName.contains('teh') || lowerName.contains('tea') ||
               lowerName.contains('matcha') || lowerName.contains('green tea')) {
      return 'Teh';
    } else if (lowerName.contains('jus') || lowerName.contains('juice') ||
               lowerName.contains('smoothie') || lowerName.contains('milkshake')) {
      return 'Minuman';
    } else if (lowerName.contains('nasi') || lowerName.contains('mie') ||
               lowerName.contains('ayam') || lowerName.contains('sate') ||
               lowerName.contains('gado') || lowerName.contains('soto')) {
      return 'Makanan Utama';
    } else if (lowerName.contains('cake') || lowerName.contains('kue') ||
               lowerName.contains('roti') || lowerName.contains('donat') ||
               lowerName.contains('cookies') || lowerName.contains('pie')) {
      return 'Dessert';
    } else if (lowerName.contains('snack') || lowerName.contains('keripik') ||
               lowerName.contains('gorengan') || lowerName.contains('bakso')) {
      return 'Snack';
    }
    
    return 'Lainnya';
  }

  // Calculate average confidence from recognized text
  double _calculateAverageConfidence(RecognizedText recognizedText) {
    if (recognizedText.blocks.isEmpty) return 0.0;
    
    double totalConfidence = 0.0;
    int elementCount = 0;
    
    for (TextBlock block in recognizedText.blocks) {
      for (TextLine line in block.lines) {
        totalConfidence += _getLineConfidence(line);
        elementCount++;
      }
    }
    
    return elementCount > 0 ? totalConfidence / elementCount : 0.0;
  }

  // Get confidence for a text line
  double _getLineConfidence(TextLine line) {
    // MLKit doesn't provide confidence directly, so we estimate based on text quality
    String text = line.text;
    
    // Basic heuristics for text quality
    double confidence = 1.0;
    
    // Penalize very short text
    if (text.length < 3) confidence *= 0.5;
    
    // Penalize text with many special characters
    int specialChars = RegExp(r'[^\w\s]').allMatches(text).length;
    if (specialChars > text.length * 0.3) confidence *= 0.7;
    
    // Penalize text with inconsistent case
    if (RegExp(r'[a-z][A-Z]').hasMatch(text)) confidence *= 0.8;
    
    return confidence.clamp(0.0, 1.0);
  }

  // Validate OCR result
  bool validateOCRResult(OCRResult result) {
    if (!result.isSuccess) {
      return false;
    }
    
    // If we have extracted text but no menu items, still allow processing
    // The AI can work with raw text even without structured menu items
    if (result.rawText.isNotEmpty && result.rawText.length > 10) {
      return true;
    }
    
    // Check if we have reasonable menu items
    int validItems = result.menuItems.where((item) => 
      item.name.length > 2 && 
      item.price > 0 && 
      item.price < 1000000 // Reasonable price limit
    ).length;
    
    return validItems >= 1;
  }

  // Dispose resources
  void dispose() {
    _textRecognizer.close();
  }
}

// OCR Result model
class OCRResult {
  final String rawText;
  final String processedText;
  final List<MenuItemOCR> menuItems;
  final double confidence;
  final bool isSuccess;
  final String? error;

  OCRResult({
    required this.rawText,
    required this.processedText,
    required this.menuItems,
    required this.confidence,
    required this.isSuccess,
    this.error,
  });

  bool get hasMenuItems => menuItems.isNotEmpty;
  int get menuItemCount => menuItems.length;
  
  String get summary {
    if (!isSuccess) return 'OCR gagal: ${error ?? "Unknown error"}';
    return 'Ditemukan ${menuItems.length} item menu dengan confidence ${(confidence * 100).toStringAsFixed(1)}%';
  }
}

// Menu Item from OCR
class MenuItemOCR {
  final String name;
  final double price;
  final String description;
  final String category;

  MenuItemOCR({
    required this.name,
    required this.price,
    required this.description,
    required this.category,
  });

  MenuItemOCR copyWith({
    String? name,
    double? price,
    String? description,
    String? category,
  }) {
    return MenuItemOCR(
      name: name ?? this.name,
      price: price ?? this.price,
      description: description ?? this.description,
      category: category ?? this.category,
    );
  }

  String get formattedPrice {
    return 'Rp ${price.toStringAsFixed(0).replaceAllMapped(
      RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'),
      (Match m) => '${m[1]}.',
    )}';
  }

  Map<String, dynamic> toJson() {
    return {
      'name': name,
      'price': price,
      'description': description,
      'category': category,
    };
  }
}
