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

#ifndef CONTENT_BROWSER_WEBUI_WEB_UI_MANAGED_INTERFACE_H_
#define CONTENT_BROWSER_WEBUI_WEB_UI_MANAGED_INTERFACE_H_

#include <memory>
#include <utility>

#include "base/memory/raw_ptr.h"
#include "base/supports_user_data.h"
#include "content/common/content_export.h"
#include "content/public/browser/web_ui_controller.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"

namespace content {

CONTENT_EXPORT void RemoveWebUIManagedInterfaces(
    WebUIController* webui_controller);

namespace internal {

class CONTENT_EXPORT WebUIManagedInterfaceBase {
 public:
  virtual ~WebUIManagedInterfaceBase() = default;
};

// Stores an interface implementation instance in the WebUI host Document.
// This is called when constructing an implementation instance in
// WebUIManagedInterface::Create().
void CONTENT_EXPORT
SaveWebUIManagedInterfaceInDocument(content::WebUIController*,
                                    std::unique_ptr<WebUIManagedInterfaceBase>);

}  // namespace internal

// WebUIManagedInterface is an optional base class for implementing classes
// that interact with Mojo endpoints e.g. implement a Mojo interface or
// communicate with a Mojo Remote.
//
// This class provides automated handling of Mojo endpoint bindings and
// lifetime management. The subclass object is created when Create() is
// called and destroyed when the associated WebUI document is removed or
// navigates away.
//
// To implement interface Foo, use the following code:
//   class FooImpl : public WebUIManagedInterface<FooImpl, Foo> { ... }
//
// Additionally, to communiate with remote interface Bar:
//   class FooBarImpl : public WebUIManagedInterface<FooBarImpl, Foo, Bar> { ...
//   }
//
// To implement no interface but only communicate with a remote interface:
//   class Baz : public WebUIManagedInterface<
//                            Baz,
//                            WebUIManagedInterfaceNoPageHandler,
//                            BarObserver> { ... }
//
// TODO(crbug.com/1417272): provide helpers to retrieve InterfaceImpl objects.
template <typename InterfaceImpl, typename... Interfaces>
class WebUIManagedInterface;

template <typename InterfaceImpl, typename PageHandler, typename Page>
class WebUIManagedInterface<InterfaceImpl, PageHandler, Page>
    : public PageHandler, public internal::WebUIManagedInterfaceBase {
 public:
  static void Create(content::WebUIController*,
                     mojo::PendingReceiver<PageHandler>,
                     mojo::PendingRemote<Page>);

  // Invoked when Mojo endpoints are bound and ready to use.
  // TODO(crbug.com/1417260): Remove this by saving endpoints in an external
  // storage before constructing `InterfaceImpl`.
  virtual void OnReady() {}

  mojo::Receiver<PageHandler>& receiver() {
    CHECK(ready_) << "Mojo endpoints are not ready. Please use OnReady().";
    return page_handler_receiver_;
  }
  mojo::Remote<Page>& remote() {
    CHECK(ready_) << "Mojo endpoints are not ready. Please use OnReady().";
    return page_remote_;
  }
  content::WebUIController* webui_controller() {
    CHECK(ready_) << "webui_controller() is not ready. Please use OnReady().";
    return webui_controller_;
  }

 private:
  bool ready_ = false;
  mojo::Receiver<PageHandler> page_handler_receiver_{this};
  mojo::Remote<Page> page_remote_;
  raw_ptr<content::WebUIController> webui_controller_;
};

template <typename InterfaceImpl, typename PageHandler>
class WebUIManagedInterface<InterfaceImpl, PageHandler>
    : public PageHandler, public internal::WebUIManagedInterfaceBase {
 public:
  static void Create(content::WebUIController*,
                     mojo::PendingReceiver<PageHandler>);

  // Invoked when Mojo endpoints are bound and ready to use.
  // TODO(crbug.com/1417260): Remove this by saving endpoints in an external
  // storage before constructing `InterfaceImpl`.
  virtual void OnReady() {}

  mojo::Receiver<PageHandler>& receiver() {
    CHECK(ready_) << "Mojo endpoints are not ready. Please use OnReady().";
    return page_handler_receiver_;
  }
  content::WebUIController* webui_controller() {
    CHECK(ready_) << "webui_controller() is not ready. Please use OnReady().";
    return webui_controller_;
  }

 private:
  bool ready_ = false;
  mojo::Receiver<PageHandler> page_handler_receiver_{this};
  raw_ptr<content::WebUIController> webui_controller_;
};

using WebUIManagedInterfaceNoPageHandler = void;

template <typename InterfaceImpl, typename Page>
class WebUIManagedInterface<InterfaceImpl,
                            WebUIManagedInterfaceNoPageHandler,
                            Page> : public internal::WebUIManagedInterfaceBase {
 public:
  static void Create(content::WebUIController*, mojo::PendingRemote<Page>);

  // Invoked when Mojo endpoints are bound and ready to use.
  // TODO(crbug.com/1417260): Remove this by saving endpoints in an external
  // storage before constructing `InterfaceImpl`.
  virtual void OnReady() {}

  mojo::Remote<Page>& remote() {
    CHECK(ready_) << "Mojo endpoints are not ready. Please use OnReady().";
    return page_remote_;
  }
  content::WebUIController* webui_controller() {
    CHECK(ready_) << "webui_controller() is not ready. Please use OnReady().";
    return webui_controller_;
  }

 private:
  bool ready_ = false;
  mojo::Remote<Page> page_remote_;
  raw_ptr<content::WebUIController> webui_controller_;
};

template <typename InterfaceImpl, typename PageHandler, typename Page>
void WebUIManagedInterface<InterfaceImpl, PageHandler, Page>::Create(
    content::WebUIController* webui_controller,
    mojo::PendingReceiver<PageHandler> pending_receiver,
    mojo::PendingRemote<Page> pending_remote) {
  auto interface_impl = std::make_unique<InterfaceImpl>();
  auto* interface_impl_ptr = interface_impl.get();
  interface_impl->webui_controller_ = webui_controller;
  interface_impl->page_handler_receiver_.Bind(std::move(pending_receiver));
  interface_impl->page_remote_.Bind(std::move(pending_remote));
  internal::SaveWebUIManagedInterfaceInDocument(webui_controller,
                                                std::move(interface_impl));
  interface_impl_ptr->ready_ = true;
  interface_impl_ptr->OnReady();
}

template <typename InterfaceImpl, typename PageHandler>
void WebUIManagedInterface<InterfaceImpl, PageHandler>::Create(
    content::WebUIController* webui_controller,
    mojo::PendingReceiver<PageHandler> pending_receiver) {
  auto interface_impl = std::make_unique<InterfaceImpl>();
  auto* interface_impl_ptr = interface_impl.get();
  interface_impl->webui_controller_ = webui_controller;
  interface_impl->page_handler_receiver_.Bind(std::move(pending_receiver));
  internal::SaveWebUIManagedInterfaceInDocument(webui_controller,
                                                std::move(interface_impl));
  interface_impl_ptr->ready_ = true;
  interface_impl_ptr->OnReady();
}

template <typename InterfaceImpl, typename Page>
void WebUIManagedInterface<InterfaceImpl, void, Page>::Create(
    content::WebUIController* webui_controller,
    mojo::PendingRemote<Page> pending_remote) {
  auto interface_impl = std::make_unique<InterfaceImpl>();
  auto* interface_impl_ptr = interface_impl.get();
  interface_impl->webui_controller_ = webui_controller;
  interface_impl->page_remote_.Bind(std::move(pending_remote));
  internal::SaveWebUIManagedInterfaceInDocument(webui_controller,
                                                std::move(interface_impl));
  interface_impl_ptr->ready_ = true;
  interface_impl_ptr->OnReady();
}

}  // namespace content

#endif  // CONTENT_BROWSER_WEBUI_WEB_UI_MANAGED_INTERFACE_H_