import 'dart:io' show Directory, File;

import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/foundation.dart' show visibleForTesting;
import 'package:flutter/services.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:path_provider/path_provider.dart';

import '../src/rust/api.dart' as rust;

const String appSemverBaseline = 'v0.1.0';
const String _sileroVadAsset = 'assets/models/silero_vad.onnx';

typedef StorageDirectoryProvider = Future<Directory> Function();
typedef StorageInitializer = Future<void> Function(String dir);

Future<void>? _storageInitFuture;
Future<void>? _vadBootstrapFuture;
StorageDirectoryProvider _storageDirectoryProvider =
    getApplicationSupportDirectory;
StorageInitializer _storageInitializer = _defaultStorageInitializer;

Future<void> _defaultStorageInitializer(String dir) {
  return rust.initStorage(dir: dir);
}

Future<File> _copyBundledAssetToDocuments({
  required String assetPath,
  required String fileName,
}) async {
  final dir = await getApplicationDocumentsDirectory();
  final file = File('${dir.path}/$fileName');
  final data = await rootBundle.load(assetPath);
  final bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);

  if (await file.exists() && await file.length() == bytes.length) {
    return file;
  }

  await file.writeAsBytes(bytes, flush: true);
  return file;
}

Future<void> configureBundledVadModels() async {
  final existing = _vadBootstrapFuture;
  if (existing != null) {
    await existing;
    return;
  }

  final bootstrap = _configureBundledVadModelsImpl();
  _vadBootstrapFuture = bootstrap;
  try {
    await bootstrap;
  } catch (_) {
    _vadBootstrapFuture = null;
    rethrow;
  }
}

Future<void> _configureBundledVadModelsImpl() async {
  final silero = await _copyBundledAssetToDocuments(
    assetPath: _sileroVadAsset,
    fileName: 'silero_vad.onnx',
  );
  await rust.setVadModelPath(path: silero.path);
}

/// Resolve the human-readable app version displayed in About.
///
/// The semver baseline is kept in code because iOS strips pre-release
/// identifiers from `CFBundleShortVersionString`; `package_info_plus`
/// still provides the platform build counter.
Future<String> resolveAppVersion({
  String semverBaseline = appSemverBaseline,
}) async {
  try {
    final info = await PackageInfo.fromPlatform();
    return appVersionFromBuildNumber(
      semverBaseline: semverBaseline,
      buildNumber: info.buildNumber,
    );
  } catch (_) {
    return semverBaseline;
  }
}

String appVersionFromBuildNumber({
  required String semverBaseline,
  required String buildNumber,
}) {
  final build = buildNumber.isEmpty ? '' : '+$buildNumber';
  return '$semverBaseline$build';
}

Future<void> wireStorage() async {
  final existing = _storageInitFuture;
  if (existing != null) {
    await existing;
    return;
  }

  final initFuture = _wireStorageImpl();
  _storageInitFuture = initFuture;
  await initFuture;
}

Future<void> _wireStorageImpl() async {
  var initialized = false;
  try {
    final dir = await _storageDirectoryProvider();
    await _storageInitializer(dir.path);
    initialized = true;
  } catch (_) {
    // Best-effort; missing storage just means no identity persistence
    // and no bookmark list this session.
  } finally {
    if (!initialized) {
      _storageInitFuture = null;
    }
  }
}

@visibleForTesting
void debugResetStorageBootstrap({
  StorageDirectoryProvider? storageDirectoryProvider,
  StorageInitializer? storageInitializer,
}) {
  _storageInitFuture = null;
  _vadBootstrapFuture = null;
  _storageDirectoryProvider =
      storageDirectoryProvider ?? getApplicationSupportDirectory;
  _storageInitializer = storageInitializer ?? _defaultStorageInitializer;
}

rust.BridgeNetworkState _mapConnectivity(List<ConnectivityResult> results) {
  if (results.isEmpty) return rust.BridgeNetworkState.unknown;
  final allNone = results.every((r) => r == ConnectivityResult.none);
  if (allNone) return rust.BridgeNetworkState.offline;
  return rust.BridgeNetworkState.online;
}

Future<void> wireConnectivity() async {
  final connectivity = Connectivity();
  try {
    final initial = await connectivity.checkConnectivity();
    rust.setNetworkState(state: _mapConnectivity(initial));
  } catch (_) {}
  connectivity.onConnectivityChanged.listen((results) {
    rust.setNetworkState(state: _mapConnectivity(results));
  });
}
