import 'dart:io';
import 'dart:math' as math;

import 'package:image/image.dart' as img;

/// Holds the two vector embeddings: one for the cropped product image,
/// one for the OCR-extracted label text.
class VectorEmbeddingResult {
  final List<double> imageVector;
  final List<double> textVector;

  const VectorEmbeddingResult({
    required this.imageVector,
    required this.textVector,
  });
}

/// Computes vector embeddings entirely on-device — no backend call.
///
/// NOTE: these are lightweight placeholder embeddings (a downsampled
/// grayscale pixel vector for the image, a character-hashing vector for
/// the text), NOT a trained model like CLIP. They're deterministic and
/// good enough to wire up printing/plumbing now. Swap `_imageVector` /
/// `_textVector` for a real on-device model (e.g. a TFLite feature
/// extractor) or a backend call later without touching the call site.
class VectorService {
  static const int _imageVectorSize = 64; // 8x8 downsampled grayscale
  static const int _textVectorSize = 32; // hashed char buckets

  static Future<VectorEmbeddingResult> getEmbeddings({
    required String imagePath,
    required String labelText,
  }) async {
    final imageVector = await _imageVector(imagePath);
    final textVector = _textVector(labelText);

    return VectorEmbeddingResult(
      imageVector: imageVector,
      textVector: textVector,
    );
  }

  /// Downsamples the image to an 8x8 grayscale grid and flattens it into
  /// a normalized (0-1) vector of length [_imageVectorSize].
  static Future<List<double>> _imageVector(String imagePath) async {
    final bytes = await File(imagePath).readAsBytes();
    final decoded = img.decodeImage(bytes);
    if (decoded == null) {
      return List<double>.filled(_imageVectorSize, 0);
    }

    final side = math.sqrt(_imageVectorSize).round(); // 8
    final resized = img.copyResize(decoded, width: side, height: side);
    final gray = img.grayscale(resized);

    final vector = <double>[];
    for (var y = 0; y < side; y++) {
      for (var x = 0; x < side; x++) {
        final pixel = gray.getPixel(x, y);
        vector.add(pixel.r / 255.0); // grayscale => r == g == b
      }
    }
    return vector;
  }

  /// Simple character-hashing bag-of-characters vector, normalized so
  /// values sum to 1 (empty text => all zeros).
  static List<double> _textVector(String text) {
    final vector = List<double>.filled(_textVectorSize, 0);
    final normalized = text.toLowerCase();
    if (normalized.isEmpty) return vector;

    for (final rune in normalized.runes) {
      final bucket = rune % _textVectorSize;
      vector[bucket] += 1;
    }

    final total = vector.fold<double>(0, (sum, v) => sum + v);
    if (total > 0) {
      for (var i = 0; i < vector.length; i++) {
        vector[i] = vector[i] / total;
      }
    }
    return vector;
  }
}