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

#ifndef COMPONENTS_PAINT_PREVIEW_BROWSER_PAINT_PREVIEW_BASE_SERVICE_H_
#define COMPONENTS_PAINT_PREVIEW_BROWSER_PAINT_PREVIEW_BASE_SERVICE_H_

#include <memory>

#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "base/unguessable_token.h"
#include "build/build_config.h"
#include "components/paint_preview/browser/paint_preview_file_mixin.h"
#include "components/paint_preview/browser/paint_preview_policy.h"
#include "components/paint_preview/common/capture_result.h"
#include "components/paint_preview/common/file_utils.h"
#include "components/paint_preview/common/mojom/paint_preview_recorder.mojom.h"
#include "components/paint_preview/common/mojom/paint_preview_types.mojom.h"
#include "components/paint_preview/common/proto/paint_preview.pb.h"
#include "components/paint_preview/common/redaction_params.h"
#include "components/paint_preview/common/serialized_recording.h"
#include "content/public/browser/web_contents.h"

namespace paint_preview {

// A base class that serves as the Public API for Paint Previews.
// Features that want to use Paint Previews should extend this class.
// This service supports both in-memery and in-file captures.
//
// The KeyedService provides a 1:1 mapping between the service and a key or
// profile allowing each feature built on Paint Previews to reliably store
// necessary data to the right directory on disk.
//
// [NOTE] for file system captures:
// - PaintPreviewFileMixin object needs to be supplied.
// - Implementations of the service should be created by implementing a factory
//   that extends one of:
//   - BrowserContextKeyedServiceFactory
//   OR preferably the
//   - SimpleKeyedServiceFactory
class PaintPreviewBaseService : public KeyedService {
 public:
  enum class CaptureStatus : int {
    kOk = 0,
    kContentUnsupported,
    kClientCreationFailed,
    kCaptureFailed,
  };

  struct CaptureParams {
    CaptureParams();

    CaptureParams(const CaptureParams&);
    CaptureParams& operator=(const CaptureParams&);
    CaptureParams(CaptureParams&&);
    CaptureParams& operator=(CaptureParams&&);

    raw_ptr<content::WebContents> web_contents = nullptr;

    // In case of specifying, an individual |render_frame_host| and its
    // descendents will be captured. In case of nullptr, full page contents will
    // be captured.
    //
    // Generally, leaving this as nullptr is what you should be doing for most
    // features. Specifying a |render_frame_host| is intended for capturing
    // individual subframes and should be used for only a few use cases.
    raw_ptr<content::RenderFrameHost> render_frame_host = nullptr;

    // Store artifacts in the file system or in memory buffers.
    RecordingPersistence persistence;

    // |root_dir| should be created using
    // GetFileManager()->CreateOrGetDirectoryFor(). However, to provide
    // flexibility in managing the lifetime of created objects and ease cleanup
    // if a capture fails the service implementation is responsible for
    // implementing this management and tracking the directories in existence.
    // Data in a directory will contain:
    // - a number of SKPs listed as <guid>.skp (one per frame)
    //
    // Will be ignored if persistence = kMemoryBuffer
    raw_ptr<const base::FilePath> root_dir = nullptr;

    // The captured area is clipped to |clip_rect| if it is non-zero.
    gfx::Rect clip_rect;

    // If not `kNone`, overrides the x coordinate of `clip_rect` with some
    // renderer-computed value.
    mojom::ClipCoordOverride clip_x_coord_override =
        mojom::ClipCoordOverride::kNone;
    // If not `kNone`, overrides the y coordinate of `clip_rect` with some
    // renderer-computed value.
    mojom::ClipCoordOverride clip_y_coord_override =
        mojom::ClipCoordOverride::kNone;

    // Whether to record links.
    bool capture_links{false};

    // Cap the perframe SkPicture size to |max_per_capture_size| if non-zero.
    size_t max_per_capture_size{0};

    // Limit on the maximum size of a decoded image that can be serialized.
    // Any images with a decoded size exceeding this value will be discarded.
    // This can be used to reduce the chance of an OOM during serialization and
    // later during playback.
    uint64_t max_decoded_image_size_bytes{std::numeric_limits<uint64_t>::max()};

    // This flag will skip GPU accelerated content where applicable when
    // capturing. This reduces hangs, capture time and may also reduce OOM
    // crashes, but results in a lower fideltiy capture (i.e. the contents
    // captured may not accurately reflect the content visible to the user at
    // time of capture).
    //
    // At present this flag:
    // - Shows a poster or blank space instead of live video frames.
    bool skip_accelerated_content{false};

    // Params for redacting content from the screenshot.
    RedactionParams redaction_params;
  };

  using OnCapturedCallback =
      base::OnceCallback<void(CaptureStatus, std::unique_ptr<CaptureResult>)>;

  // Creates a service instance for a feature. Artifacts produced will live in
  // |profile_dir|/paint_preview/|ascii_feature_name|. Implementers of the
  // factory can also elect their factory to not construct services in the event
  // a profile |is_off_the_record|. The |policy| object is responsible for
  // determining whether or not a given WebContents is amenable to paint
  // preview. If nullptr is passed as |policy| all content is deemed amenable.
  //
  // NOTE: Pass nullptr as |file_mixin| if you're planning to use the service
  // for only in-memory captures.
  PaintPreviewBaseService(std::unique_ptr<PaintPreviewFileMixin> file_mixin,
                          std::unique_ptr<PaintPreviewPolicy> policy,
                          bool is_off_the_record);
  ~PaintPreviewBaseService() override;

  PaintPreviewFileMixin* GetFileMixin() { return file_mixin_.get(); }

  // Returns whether the created service is off the record.
  bool IsOffTheRecord() const { return is_off_the_record_; }

  // Captures need to run on the Browser UI thread! Captures may involve child
  // frames so the PaintPreviewClient (WebContentsObserver) must be stored as
  // WebContentsUserData which is not thread safe and must only be accessible
  // from a specific sequence i.e. the UI thread.
  //
  // Captures the main frame of |capture_params.web_contents| (an observer for
  // capturing Paint Previews is created for web contents if it does not exist).
  // The capture is attributed to the URL of the main frame. On completion the
  // status of the capture is provided via |callback|.
  //
  // See |PaintPreviewBaseService::CaptureParams| for more info about the
  // capture parameters.
  void CapturePaintPreview(CaptureParams capture_params,
                           OnCapturedCallback callback);

 private:
  void OnCaptured(base::ScopedClosureRunner capture_handle,
                  base::TimeTicks start_time,
                  OnCapturedCallback callback,
                  base::UnguessableToken guid,
                  mojom::PaintPreviewStatus status,
                  std::unique_ptr<CaptureResult> result);

  std::unique_ptr<PaintPreviewFileMixin> file_mixin_;
  std::unique_ptr<PaintPreviewPolicy> policy_;
  bool is_off_the_record_;

  base::WeakPtrFactory<PaintPreviewBaseService> weak_ptr_factory_{this};

  PaintPreviewBaseService(const PaintPreviewBaseService&) = delete;
  PaintPreviewBaseService& operator=(const PaintPreviewBaseService&) = delete;
};

std::string ToString(PaintPreviewBaseService::CaptureStatus status);

}  // namespace paint_preview

#endif  // COMPONENTS_PAINT_PREVIEW_BROWSER_PAINT_PREVIEW_BASE_SERVICE_H_
