// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:file_selector_platform_interface/file_selector_platform_interface.dart';
import 'package:flutter/foundation.dart' show visibleForTesting;
import 'package:flutter/services.dart';

const MethodChannel _channel =
    MethodChannel('plugins.flutter.dev/file_selector_linux');

const String _typeGroupLabelKey = 'label';
const String _typeGroupExtensionsKey = 'extensions';
const String _typeGroupMimeTypesKey = 'mimeTypes';

const String _openFileMethod = 'openFile';
const String _getSavePathMethod = 'getSavePath';
const String _getDirectoryPathMethod = 'getDirectoryPath';

const String _acceptedTypeGroupsKey = 'acceptedTypeGroups';
const String _confirmButtonTextKey = 'confirmButtonText';
const String _initialDirectoryKey = 'initialDirectory';
const String _multipleKey = 'multiple';
const String _suggestedNameKey = 'suggestedName';

/// An implementation of [FileSelectorPlatform] for Linux.
class FileSelectorLinux extends FileSelectorPlatform {
  /// The MethodChannel that is being used by this implementation of the plugin.
  @visibleForTesting
  MethodChannel get channel => _channel;

  /// Registers the Linux implementation.
  static void registerWith() {
    FileSelectorPlatform.instance = FileSelectorLinux();
  }

  @override
  Future<XFile?> openFile({
    List<XTypeGroup>? acceptedTypeGroups,
    String? initialDirectory,
    String? confirmButtonText,
  }) async {
    final List<Map<String, Object>> serializedTypeGroups =
        _serializeTypeGroups(acceptedTypeGroups);
    final List<String>? path = await _channel.invokeListMethod<String>(
      _openFileMethod,
      <String, dynamic>{
        if (serializedTypeGroups.isNotEmpty)
          _acceptedTypeGroupsKey: serializedTypeGroups,
        'initialDirectory': initialDirectory,
        _confirmButtonTextKey: confirmButtonText,
        _multipleKey: false,
      },
    );
    return path == null ? null : XFile(path.first);
  }

  @override
  Future<List<XFile>> openFiles({
    List<XTypeGroup>? acceptedTypeGroups,
    String? initialDirectory,
    String? confirmButtonText,
  }) async {
    final List<Map<String, Object>> serializedTypeGroups =
        _serializeTypeGroups(acceptedTypeGroups);
    final List<String>? pathList = await _channel.invokeListMethod<String>(
      _openFileMethod,
      <String, dynamic>{
        if (serializedTypeGroups.isNotEmpty)
          _acceptedTypeGroupsKey: serializedTypeGroups,
        _initialDirectoryKey: initialDirectory,
        _confirmButtonTextKey: confirmButtonText,
        _multipleKey: true,
      },
    );
    return pathList?.map((String path) => XFile(path)).toList() ?? <XFile>[];
  }

  @override
  Future<String?> getSavePath({
    List<XTypeGroup>? acceptedTypeGroups,
    String? initialDirectory,
    String? suggestedName,
    String? confirmButtonText,
  }) async {
    final FileSaveLocation? location = await getSaveLocation(
        acceptedTypeGroups: acceptedTypeGroups,
        options: SaveDialogOptions(
          initialDirectory: initialDirectory,
          suggestedName: suggestedName,
          confirmButtonText: confirmButtonText,
        ));
    return location?.path;
  }

  @override
  Future<FileSaveLocation?> getSaveLocation({
    List<XTypeGroup>? acceptedTypeGroups,
    SaveDialogOptions options = const SaveDialogOptions(),
  }) async {
    final List<Map<String, Object>> serializedTypeGroups =
        _serializeTypeGroups(acceptedTypeGroups);
    // TODO(stuartmorgan): Add the selected type group here and return it. See
    // https://github.com/flutter/flutter/issues/107093
    final String? path = await _channel.invokeMethod<String>(
      _getSavePathMethod,
      <String, dynamic>{
        if (serializedTypeGroups.isNotEmpty)
          _acceptedTypeGroupsKey: serializedTypeGroups,
        _initialDirectoryKey: options.initialDirectory,
        _suggestedNameKey: options.suggestedName,
        _confirmButtonTextKey: options.confirmButtonText,
      },
    );
    return path == null ? null : FileSaveLocation(path);
  }

  @override
  Future<String?> getDirectoryPath({
    String? initialDirectory,
    String? confirmButtonText,
  }) async {
    final List<String>? path = await _channel
        .invokeListMethod<String>(_getDirectoryPathMethod, <String, dynamic>{
      _initialDirectoryKey: initialDirectory,
      _confirmButtonTextKey: confirmButtonText,
    });
    return path?.first;
  }

  @override
  Future<List<String>> getDirectoryPaths({
    String? initialDirectory,
    String? confirmButtonText,
  }) async {
    final List<String>? pathList = await _channel
        .invokeListMethod<String>(_getDirectoryPathMethod, <String, dynamic>{
      _initialDirectoryKey: initialDirectory,
      _confirmButtonTextKey: confirmButtonText,
      _multipleKey: true,
    });
    return pathList ?? <String>[];
  }
}

List<Map<String, Object>> _serializeTypeGroups(List<XTypeGroup>? groups) {
  return (groups ?? <XTypeGroup>[]).map(_serializeTypeGroup).toList();
}

Map<String, Object> _serializeTypeGroup(XTypeGroup group) {
  final Map<String, Object> serialization = <String, Object>{
    _typeGroupLabelKey: group.label ?? '',
  };
  if (group.allowsAny) {
    serialization[_typeGroupExtensionsKey] = <String>['*'];
  } else {
    if ((group.extensions?.isEmpty ?? true) &&
        (group.mimeTypes?.isEmpty ?? true)) {
      throw ArgumentError('Provided type group $group does not allow '
          'all files, but does not set any of the Linux-supported filter '
          'categories. "extensions" or "mimeTypes" must be non-empty for Linux '
          'if anything is non-empty.');
    }
    if (group.extensions?.isNotEmpty ?? false) {
      serialization[_typeGroupExtensionsKey] = group.extensions
              ?.map((String extension) => '*.$extension')
              .toList() ??
          <String>[];
    }
    if (group.mimeTypes?.isNotEmpty ?? false) {
      serialization[_typeGroupMimeTypesKey] = group.mimeTypes ?? <String>[];
    }
  }
  return serialization;
}
