// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef UI_SHELL_DIALOGS_SELECT_FILE_DIALOG_LINUX_PORTAL_H_
#define UI_SHELL_DIALOGS_SELECT_FILE_DIALOG_LINUX_PORTAL_H_

#include <memory>
#include <optional>
#include <string>
#include <tuple>
#include <vector>

#include "base/functional/callback_forward.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/task/sequenced_task_runner.h"
#include "components/dbus/xdg/request.h"
#include "dbus/bus.h"
#include "ui/shell_dialogs/select_file_dialog_linux.h"
#include "ui/shell_dialogs/shell_dialogs_export.h"
#include "url/gurl.h"

namespace ui {

using OnSelectFileExecutedCallback =
    base::OnceCallback<void(std::vector<base::FilePath> paths,
                            std::string current_filter)>;
using OnSelectFileCanceledCallback = base::OnceCallback<void()>;

// Implementation of SelectFileDialog that has the XDG file chooser portal show
// a platform-dependent file selection dialog. This acts as a modal dialog.
class SHELL_DIALOGS_EXPORT SelectFileDialogLinuxPortal
    : public SelectFileDialogLinux {
 public:
  SelectFileDialogLinuxPortal(Listener* listener,
                              std::unique_ptr<ui::SelectFilePolicy> policy);

  SelectFileDialogLinuxPortal(const SelectFileDialogLinuxPortal& other) =
      delete;
  SelectFileDialogLinuxPortal& operator=(
      const SelectFileDialogLinuxPortal& other) = delete;

 protected:
  ~SelectFileDialogLinuxPortal() override;

  // BaseShellDialog:
  void ListenerDestroyed() override;
  bool IsRunning(gfx::NativeWindow parent_window) const override;

  // SelectFileDialog:
  void SelectFileImpl(Type type,
                      const std::u16string& title,
                      const base::FilePath& default_path,
                      const FileTypeInfo* file_types,
                      int file_type_index,
                      const base::FilePath::StringType& default_extension,
                      gfx::NativeWindow owning_window,
                      const GURL* caller) override;
  bool HasMultipleFileTypeChoicesImpl() override;

  base::WeakPtr<SelectFileDialogLinuxPortal> GetWeakPtrForTesting() {
    return weak_factory_.GetWeakPtr();
  }

 private:
  // Glob-style patterns are indicated by 0, MIME types by 1. Patterns are
  // case-sensitive.
  using DbusFilterPattern = std::tuple<uint32_t, std::string>;
  using DbusFilterPatterns = std::vector<DbusFilterPattern>;
  // The first string is a user-visible name for the filter.
  using DbusFilter = std::tuple<std::string, DbusFilterPatterns>;
  using DbusFilters = std::vector<DbusFilter>;

  // A named set of patterns used as a dialog filter.
  struct PortalFilter {
    PortalFilter();
    PortalFilter(const PortalFilter& other);
    PortalFilter(PortalFilter&& other);
    ~PortalFilter();

    PortalFilter& operator=(const PortalFilter& other) = default;
    PortalFilter& operator=(PortalFilter&& other) = default;

    std::string name;
    std::vector<std::string> patterns;
  };

  // A set of PortalFilters, potentially with a default.
  struct PortalFilterSet {
    PortalFilterSet();
    PortalFilterSet(const PortalFilterSet& other);
    PortalFilterSet(PortalFilterSet&& other);
    ~PortalFilterSet();

    PortalFilterSet& operator=(const PortalFilterSet& other) = default;
    PortalFilterSet& operator=(PortalFilterSet&& other) = default;

    std::vector<PortalFilter> filters;
    std::optional<PortalFilter> default_filter;
  };

  PortalFilterSet BuildFilterSet();

  void SelectFileImplWithParentHandle(
      std::u16string title,
      base::FilePath default_path,
      PortalFilterSet filter_set,
      base::FilePath::StringType default_extension,
      std::string parent_handle);

  void SelectFileImplOnMainThread(std::u16string title,
                                  base::FilePath default_path,
                                  const bool default_path_exists,
                                  PortalFilterSet filter_set,
                                  base::FilePath::StringType default_extension,
                                  std::string parent_handle);

  dbus_xdg::Dictionary BuildOptionsDictionary(
      const base::FilePath& default_path,
      bool default_path_exists,
      const PortalFilterSet& filter_set);

  DbusFilter MakeFilterStruct(const PortalFilter& filter);

  void MakeFileChooserRequest(const std::string& method,
                              const std::string& title,
                              dbus_xdg::Dictionary options,
                              std::string parent_handle);

  void OnFileChooserResponse(
      base::expected<dbus_xdg::Dictionary, dbus_xdg::ResponseError> results);

  void CompleteOpen(std::vector<base::FilePath> paths,
                    std::string current_filter);

  void CancelOpen();

  void DialogCreatedOnInvoker();
  void CompleteOpenOnInvoker(std::vector<base::FilePath> paths,
                             std::string current_filter);
  void CancelOpenOnInvoker();

  // Removes the DialogInfo parent.
  void UnparentOnInvoker();

  void OnPortalAvailable(std::u16string title,
                         base::FilePath default_path,
                         base::FilePath::StringType default_extension,
                         std::optional<GURL> caller,
                         uint32_t version);

  // The task runner the SelectFileImpl method was called on.
  scoped_refptr<base::SequencedTaskRunner> invoker_task_runner_;

  // This should be used by the invoker task runner.
  base::WeakPtr<aura::WindowTreeHost> host_;

  // The fallback dialog to use if the portal is not available.
  scoped_refptr<SelectFileDialog> fallback_dialog_;

  std::vector<PortalFilter> filters_;

  // The Request representing an in-flight file chooser call, if any.
  // We keep this alive until the response arrives or until the dialog
  // is destroyed, whichever comes first.
  std::unique_ptr<dbus_xdg::Request> file_chooser_request_;

  // Event handling on the parent window is disabled while the dialog is active
  // to make the dialog modal.  This closure should be run when the dialog is
  // closed to reenable event handling.
  base::OnceClosure reenable_window_event_handling_;

  base::WeakPtrFactory<SelectFileDialogLinuxPortal> weak_factory_{this};
};

}  // namespace ui

#endif  // UI_SHELL_DIALOGS_SELECT_FILE_DIALOG_LINUX_PORTAL_H_
