import 'dart:typed_data';

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

/// Blur / sharpness result from CoinSnap `blur.tflite`.
class BlurResult {
  const BlurResult({
    required this.blurScore,
    required this.clearScore,
    required this.isBlurry,
  });

  /// Softmax-ish class 0 = blur, class 1 = clear (from model).
  final double blurScore;
  final double clearScore;
  final bool isBlurry;

  double get confidence => isBlurry ? blurScore : clearScore;
}

/// CoinSnap blur classifier (`assets/ml/blur.tflite`).
///
/// Input: `[1, 3, 224, 224]` NCHW float32, letterbox on white, pixels / 255.
/// Output: `[1, 2]` → `[blur, clear]`.
class BlurDetector {
  BlurDetector({this.clearThreshold = 0.55});

  static const int inputSize = 224;
  static const String assetPath = 'assets/ml/blur.tflite';

  /// Clear score must be ≥ this to pass (mirrors app-side threshold).
  final double clearThreshold;

  Interpreter? _interpreter;
  bool _loading = false;

  Future<void> ensureLoaded() async {
    if (_interpreter != null || _loading) return;
    _loading = true;
    try {
      _interpreter = await Interpreter.fromAsset(assetPath);
    } finally {
      _loading = false;
    }
  }

  Future<BlurResult> evaluate(img.Image image) async {
    await ensureLoaded();
    final interp = _interpreter;
    if (interp == null) {
      return const BlurResult(blurScore: 0, clearScore: 1, isBlurry: false);
    }

    final canvas = _letterboxWhite(image, inputSize);
    final flat = _toNchw(canvas);
    // Nested list matches [1, 3, 224, 224] NCHW.
    final inputNd = List.generate(
      1,
      (_) => List.generate(
        3,
        (c) => List.generate(
          inputSize,
          (y) => List.generate(inputSize, (x) {
            final i = c * inputSize * inputSize + y * inputSize + x;
            return flat[i];
          }),
        ),
      ),
    );
    final output = List.generate(1, (_) => List<double>.filled(2, 0.0));
    interp.run(inputNd, output);

    final blur = output[0][0];
    final clear = output[0][1];
    final isBlurry = clear < clearThreshold;
    return BlurResult(blurScore: blur, clearScore: clear, isBlurry: isBlurry);
  }

  Future<void> dispose() async {
    _interpreter?.close();
    _interpreter = null;
  }

  static img.Image _letterboxWhite(img.Image src, int size) {
    final scale = size / (src.width > src.height ? src.width : src.height);
    final nw = (src.width * scale).round().clamp(1, size);
    final nh = (src.height * scale).round().clamp(1, size);
    final resized = img.copyResize(
      src,
      width: nw,
      height: nh,
      interpolation: img.Interpolation.linear,
    );
    final canvas = img.Image(width: size, height: size);
    img.fill(canvas, color: img.ColorRgb8(255, 255, 255));
    final padX = ((size - nw) / 2).floor();
    final padY = ((size - nh) / 2).floor();
    img.compositeImage(canvas, resized, dstX: padX, dstY: padY);
    return canvas;
  }

  static Float32List _toNchw(img.Image canvas) {
    final out = Float32List(1 * 3 * inputSize * inputSize);
    const plane = inputSize * inputSize;
    for (var y = 0; y < inputSize; y++) {
      for (var x = 0; x < inputSize; x++) {
        final p = canvas.getPixel(x, y);
        final i = y * inputSize + x;
        out[i] = p.r / 255.0;
        out[plane + i] = p.g / 255.0;
        out[plane * 2 + i] = p.b / 255.0;
      }
    }
    return out;
  }
}
