import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';

import 'package:file_picker/src/api/file_picker_types.dart';
import 'package:file_picker/src/api/android_saf_options.dart';
import 'package:file_picker/src/platform/file_picker_platform_interface.dart';
import 'package:file_picker/src/api/file_picker_result.dart';
import 'package:file_picker/src/api/platform_file.dart';
import 'package:file_picker/src/utils/file_picker_utils.dart';
import 'package:file_picker/src/platform/linux/xdp_filechooser.dart';
import 'package:file_picker/src/platform/linux/xdp_request.dart';
import 'package:file_picker/src/platform/linux/filters.dart';
import 'package:dbus/dbus.dart';

class FilePickerLinux extends FilePickerPlatform {
  static void registerWith() {
    FilePickerPlatform.instance = FilePickerLinux();
  }

  final destination = "org.freedesktop.portal.Desktop";
  late final DBusClient _client;
  late final OrgFreedesktopPortalFileChooser _xdpChooser;

  FilePickerLinux() : super() {
    _client = DBusClient.session();
    _xdpChooser = OrgFreedesktopPortalFileChooser(
      _client,
      destination,
      path: DBusObjectPath("/org/freedesktop/portal/desktop"),
    );
  }

  @override
  Future<FilePickerResult?> pickFiles({
    String? dialogTitle,
    String? initialDirectory,
    FileType type = FileType.any,
    List<String>? allowedExtensions,
    Function(FilePickerStatus)? onFileLoading,
    bool allowMultiple = false,
    bool withData = false,
    bool withReadStream = false,
    bool lockParentWindow = false,
    bool readSequential = false,
    int compressionQuality = 0,
    bool cancelUploadOnWindowBlur = true,
    AndroidSAFOptions? androidSafOptions,
  }) async {
    final filter = Filter(type, allowedExtensions);
    Map<String, DBusValue> xdpOption = {
      'handle_token': DBusString('flutter_picker'),
      'multiple': DBusBoolean(allowMultiple),
      'modal': DBusBoolean(lockParentWindow),
      'filters': filter.toDBusArray(),
    };
    if (initialDirectory != null) {
      final Uint8List tmp = _encodeDirectory(initialDirectory);
      DBusArray directory = DBusArray.byte(tmp);
      xdpOption["current_folder"] = directory;
    }
    final replyPath = await _xdpChooser.callOpenFile(
      "",
      dialogTitle ?? "flutter picker",
      xdpOption,
    );

    List<Uri> uriPaths = [];

    final request = OrgFreedesktopPortalRequest(
      _client,
      destination,
      path: replyPath,
    );

    await for (var response in request.response) {
      final status = response.response;
      // Maybe cancelled
      if (status != 0) {
        return null;
      }
      final result = response.results;
      uriPaths =
          result["uris"]
              ?.asArray()
              .map((data) => Uri.parse(data.asString()))
              .toList() ??
          [];
      break;
    }

    final filePaths = uriPaths.map((uri) => uri.toFilePath()).toList();

    final List<PlatformFile> platformFiles =
        await FilePickerUtils.filePathsToPlatformFiles(
          filePaths,
          withReadStream: withReadStream,
          withData: withData,
        );

    return FilePickerResult(platformFiles);
  }

  @override
  Future<String?> getDirectoryPath({
    String? dialogTitle,
    bool lockParentWindow = false,
    String? initialDirectory,
    AndroidSAFOptions? androidSafOptions,
  }) async {
    Map<String, DBusValue> xdpOption = {
      'handle_token': DBusString('flutter_picker'),
      'directory': DBusBoolean(true),
      'modal': DBusBoolean(lockParentWindow),
    };
    if (initialDirectory != null) {
      final Uint8List tmp = _encodeDirectory(initialDirectory);
      DBusArray directory = DBusArray.byte(tmp);
      xdpOption["current_folder"] = directory;
    }
    final replyPath = await _xdpChooser.callOpenFile(
      "",
      dialogTitle ?? "flutter picker",
      xdpOption,
    );

    List<Uri> uriPaths = [];

    final request = OrgFreedesktopPortalRequest(
      _client,
      destination,
      path: replyPath,
    );

    await for (var response in request.response) {
      final status = response.response;
      // Maybe cancelled
      if (status != 0) {
        return null;
      }
      final result = response.results;
      uriPaths =
          result["uris"]
              ?.asArray()
              .map((data) => Uri.parse(data.asString()))
              .toList() ??
          [];
      break;
    }

    final filePaths = uriPaths.map((uri) => uri.toFilePath()).toList();

    final List<PlatformFile> platformFiles =
        await FilePickerUtils.filePathsToPlatformFiles(filePaths);

    return platformFiles.firstOrNull?.path;
  }

  @override
  Future<String?> saveFile({
    String? dialogTitle,
    required String fileName,
    String? initialDirectory,
    FileType type = FileType.any,
    List<String>? allowedExtensions,
    required Uint8List bytes,
    Function(FilePickerStatus)? onFileLoading,
    bool lockParentWindow = false,
  }) async {
    Map<String, DBusValue> xdpOption = {
      'handle_token': DBusString('flutter_picker'),
      'current_name': DBusString(fileName),
      'modal': DBusBoolean(lockParentWindow),
    };
    if (initialDirectory != null) {
      final Uint8List tmp = _encodeDirectory(initialDirectory);
      DBusArray directory = DBusArray.byte(tmp);
      xdpOption["current_folder"] = directory;
    }

    final replyPath = await _xdpChooser.callSaveFile(
      "",
      dialogTitle ?? "flutter picker",
      xdpOption,
    );
    final request = OrgFreedesktopPortalRequest(
      _client,
      destination,
      path: replyPath,
    );

    List<Uri> saveUris = [];
    await for (var response in request.response) {
      final status = response.response;
      // Maybe cancelled
      if (status != 0) {
        return null;
      }
      final result = response.results;
      saveUris =
          result["uris"]
              ?.asArray()
              .map((data) => Uri.parse(data.asString()))
              .toList() ??
          saveUris;
      break;
    }

    final savedFilePaths = saveUris.map((uri) => uri.toFilePath()).toList();
    final savedFilePath = savedFilePaths.firstOrNull;

    await FilePickerUtils.saveBytesToFile(bytes, savedFilePath);

    return savedFilePath;
  }

  Uint8List _encodeDirectory(String initialDirectory) {
    return Uint8List.fromList([...utf8.encode(initialDirectory), 0]);
  }
}
