import 'dart:io';
import 'dart:ui' as ui;

import 'package:flutter/material.dart';
import 'package:image/image.dart' as img;
import 'package:path_provider/path_provider.dart';

import '../widgets/guide_frame.dart';
import 'blur_detector.dart';
import 'coin_detector.dart';

enum CoinGateFail { none, blurry, noCoin }

class CoinGateResult {
  const CoinGateResult({
    required this.ok,
    this.fail = CoinGateFail.none,
    this.blur,
    this.box,
  });

  final bool ok;
  final CoinGateFail fail;
  final BlurResult? blur;
  final CoinBox? box;
}

/// Post-capture gate: blur.tflite → detect.tflite (CoinSnap order).
class CoinScanGate {
  static Future<CoinGateResult> evaluate({
    required String imagePath,
    Size? captureViewport,
    CoinDetector? detector,
    BlurDetector? blurDetector,
  }) async {
    final ownedDet = detector == null;
    final ownedBlur = blurDetector == null;
    final det = detector ?? CoinDetector();
    final blur = blurDetector ?? BlurDetector();

    try {
      final raster = await _guideCrop(imagePath, captureViewport);
      if (raster == null) {
        return const CoinGateResult(ok: false, fail: CoinGateFail.noCoin);
      }

      final blurResult = await blur.evaluate(raster);
      if (blurResult.isBlurry) {
        return CoinGateResult(
          ok: false,
          fail: CoinGateFail.blurry,
          blur: blurResult,
        );
      }

      final boxes = await det.detectImage(raster);
      final best = det.pickBest(
        boxes,
        imageSize: Size(raster.width.toDouble(), raster.height.toDouble()),
      );
      if (best == null) {
        return CoinGateResult(
          ok: false,
          fail: CoinGateFail.noCoin,
          blur: blurResult,
        );
      }
      return CoinGateResult(ok: true, blur: blurResult, box: best);
    } finally {
      if (ownedDet) await det.dispose();
      if (ownedBlur) await blur.dispose();
    }
  }

  static Future<img.Image?> _guideCrop(
    String imagePath,
    Size? captureViewport,
  ) async {
    final raw = await File(imagePath).readAsBytes();
    final codec = await ui.instantiateImageCodec(raw);
    final frame = await codec.getNextFrame();
    final fullImage = frame.image;
    final rgba = await fullImage.toByteData(format: ui.ImageByteFormat.rawRgba);
    if (rgba == null) {
      fullImage.dispose();
      return null;
    }

    final fullRaster = img.Image.fromBytes(
      width: fullImage.width,
      height: fullImage.height,
      bytes: rgba.buffer,
      bytesOffset: rgba.offsetInBytes,
      numChannels: 4,
      order: img.ChannelOrder.rgba,
    );
    fullImage.dispose();

    final imageSize = Size(
      fullRaster.width.toDouble(),
      fullRaster.height.toDouble(),
    );
    final guide = captureViewport != null
        ? GuideFrame.inCoverImage(viewport: captureViewport, imageSize: imageSize)
        : GuideFrame.inImage(imageSize);

    final gx = guide.left.round().clamp(0, fullRaster.width - 1);
    final gy = guide.top.round().clamp(0, fullRaster.height - 1);
    final gw = guide.width.round().clamp(1, fullRaster.width - gx);
    final gh = guide.height.round().clamp(1, fullRaster.height - gy);
    return img.copyCrop(fullRaster, x: gx, y: gy, width: gw, height: gh);
  }

  /// Persist guide crop for preview reuse.
  static Future<String> writeGuideJpeg(img.Image raster) async {
    final dir = await getTemporaryDirectory();
    final path =
        '${dir.path}/coin_guide_${DateTime.now().millisecondsSinceEpoch}.jpg';
    await File(path).writeAsBytes(img.encodeJpg(raster, quality: 95), flush: true);
    return path;
  }
}
