import 'dart:ffi';
import 'dart:io';

import 'package:ffi/ffi.dart';

import 'phash_bindings.dart';

/// Loads the vendored C++ `libphash` for the current platform.
DynamicLibrary loadPhashLibrary({String? hostLibraryPath}) {
  if (hostLibraryPath != null && hostLibraryPath.isNotEmpty) {
    return DynamicLibrary.open(hostLibraryPath);
  }

  if (Platform.isAndroid) {
    return DynamicLibrary.open('libphash.so');
  }
  if (Platform.isIOS) {
    try {
      return DynamicLibrary.process();
    } catch (_) {
      return DynamicLibrary.open('phash.framework/phash');
    }
  }
  if (Platform.isMacOS) {
    final candidates = <String>[
      'native/phash/build/libphash.dylib',
      'native/phash/build/libphash.so',
      'libphash.dylib',
      'libphash.so',
    ];
    for (final path in candidates) {
      final file = File(path);
      if (file.existsSync()) {
        return DynamicLibrary.open(file.absolute.path);
      }
    }
    return DynamicLibrary.open('libphash.dylib');
  }
  if (Platform.isLinux) {
    final candidates = <String>[
      'native/phash/build/libphash.so',
      'libphash.so',
    ];
    for (final path in candidates) {
      final file = File(path);
      if (file.existsSync()) {
        return DynamicLibrary.open(file.absolute.path);
      }
    }
    return DynamicLibrary.open('libphash.so');
  }
  throw UnsupportedError('phash FFI is not supported on ${Platform.operatingSystem}');
}

/// Thin FFI wrapper around rgb-phash-v1 C API.
class PhashNative {
  PhashNative._(this._fromBytes, this._hamming);

  factory PhashNative({String? hostLibraryPath}) {
    final lib = loadPhashLibrary(hostLibraryPath: hostLibraryPath);
    return PhashNative._(
      lib.lookupFunction<PhashFromBytesNative, PhashFromBytesDart>(
        'phash_from_bytes',
      ),
      lib.lookupFunction<PhashHammingNative, PhashHammingDart>(
        'phash_hamming',
      ),
    );
  }

  final PhashFromBytesDart _fromBytes;
  final PhashHammingDart _hamming;

  /// Compute 105-bit ASCII hash from encoded image bytes (JPEG/PNG/…).
  String hashFromBytes(List<int> bytes) {
    if (bytes.isEmpty) {
      throw ArgumentError('image bytes are empty');
    }
    final data = malloc<Uint8>(bytes.length);
    final out = malloc<Uint8>(kPhashBits + 1);
    try {
      data.asTypedList(bytes.length).setAll(0, bytes);
      final rc = _fromBytes(data, bytes.length, out.cast<Char>(), kPhashBits + 1);
      if (rc != 0) {
        throw StateError('phash_from_bytes failed with code $rc');
      }
      return out.cast<Utf8>().toDartString();
    } finally {
      malloc.free(data);
      malloc.free(out);
    }
  }

  int hamming(String a, String b) {
    final pa = a.toNativeUtf8();
    final pb = b.toNativeUtf8();
    try {
      final d = _hamming(pa.cast<Char>(), pb.cast<Char>());
      if (d < 0) {
        throw StateError('phash_hamming failed');
      }
      return d;
    } finally {
      malloc.free(pa);
      malloc.free(pb);
    }
  }
}
