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

#ifndef CHROME_BROWSER_UI_VIEWS_OMNIBOX_OMNIBOX_TEXT_VIEW_H_
#define CHROME_BROWSER_UI_VIEWS_OMNIBOX_OMNIBOX_TEXT_VIEW_H_

#include <stddef.h>

#include <memory>
#include <string_view>

#include "base/memory/raw_ptr.h"
#include "chrome/browser/ui/views/omnibox/omnibox_result_view.h"
#include "components/omnibox/browser/autocomplete_match.h"
#include "third_party/omnibox_proto/answer_type.pb.h"
#include "third_party/omnibox_proto/rich_answer_template.pb.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/gfx/font_list.h"
#include "ui/views/view.h"

namespace gfx {
class Canvas;
class RenderText;
}  // namespace gfx

// A view containing a render text styled via search results. This differs from
// the general purpose views::Label class by having less general features (such
// as selection) and more specific features (such as suggestion answer styling).
class OmniboxTextView : public views::View {
  METADATA_HEADER(OmniboxTextView, views::View)

 public:
  explicit OmniboxTextView(OmniboxResultView* result_view);
  OmniboxTextView(const OmniboxTextView&) = delete;
  OmniboxTextView& operator=(const OmniboxTextView&) = delete;
  ~OmniboxTextView() override;

  // views::View:
  gfx::Size CalculatePreferredSize(
      const views::SizeBounds& available_size) const override;
  void OnPaint(gfx::Canvas* canvas) override;

  // Applies given theme color to underlying render text. This is called Apply*
  // instead of Set* because the only state kept is in render_text, so call this
  // after setting text with methods below.
  void ApplyTextColor(ui::ColorId id);

  // Returns the render text, or an empty string if there is none.
  std::u16string_view GetText() const;

  // Used for content/description separator & tail suggest ellipses.
  void SetText(std::u16string_view new_text);

  // Used for standard suggestions.
  void SetTextWithStyling(std::u16string_view new_text,
                          const ACMatchClassifications& classifications);

  // Used for search answers using `RichAnswerTemplate`.
  // Sets the styling for `FormattedString`'s `FormattedStringFragment`s.
  // `fragment_index` specifies where to start appending and styling text from.
  // Headlines should be styled differently than subheads.
  void AppendAndStyleAnswerText(
      const omnibox::FormattedString& formatted_string,
      size_t fragment_index,
      const omnibox::AnswerType& answer_type,
      bool is_headline);

  // Used for suggestions using `RichAnswerTemplate`.
  void SetMultilineAnswerText(const omnibox::FormattedString& formatted_string,
                              const omnibox::AnswerType& answer_type);

  // Used for history embedding answers.
  void SetMultilineText(const std::u16string& text);

  // Get the height of one line of text.  This is handy if the view might have
  // multiple lines.
  int GetLineHeight() const;

  // Reapplies text styling to the results text, based on the types of the match
  // parts.
  void ReapplyStyling();

  // Creates a platform-approriate RenderText, sets its format to that of
  // a suggestion and inserts (renders) the provided |text|.
  std::unique_ptr<gfx::RenderText> CreateRenderText(
      std::u16string_view text) const;

 private:
  // Updates the cached maximum line height and recomputes the preferred size.
  void OnStyleChanged();

  // To get color values.
  raw_ptr<OmniboxResultView> result_view_;

  // Font settings for this view.
  int font_height_ = 0;

  // Whether to wrap lines if the width is too narrow for the whole string.
  // TODO(crbug.com/370088101): Ensure `wrap_text_lines_` is set correctly in
  //   the `SetTextWithStyling()` and related methods above. Can we use
  //   `render_text_->multiline()` instead? Ideally, all the text setting
  //   methods should detect if nothing has changed (i.e. text, style, and other
  //   options like wrapping / multiline, etc are all unchanged). If nothing has
  //   changed, then they should early exit because those methods are called
  //   1000's of times per keystroke and cause noticeable lag when not
  //   early-exiting. Otherwise, if something has changed, the methods should
  //   explicitly set all these states and not assume they still hold their
  //   default values from initialization since `OmniboxTextView`s are reused.
  bool wrap_text_lines_ = false;

  // The primary data for this class.
  std::unique_ptr<gfx::RenderText> render_text_;
  // The classifications most recently passed to SetText. Used to exit
  // early instead of setting text when the text and classifications
  // match the current state of the view.
  std::unique_ptr<ACMatchClassifications> cached_classifications_;

  // Caches the param and return value of `CalculatePreferredSize()` for
  // multiline texts. `CalculatePreferredSize()` is called 100's of times per
  // keystroke. For 1-line texts, it's cached via `View::SetPreferredSize()`.
  // But the size of multiline texts depends on `available_size`. Caching for
  // the most recent `available_size` reduces the need to recompute preferred
  // size from 300 to 4 times per keystroke. Increasing the cache size to 2
  // would reduce it further to 0 times per keystroke, but that seems
  // unnecessary. Unused for 1-line texts.
  // The most recent `available_size` param passed to
  // `CalculatePreferredSize()`.
  mutable views::SizeBounds cached_available_size_;
  // The most recent return value from `CalculatePreferredSize()`.
  mutable gfx::Size cached_calculate_preferred_size_;
};

#endif  // CHROME_BROWSER_UI_VIEWS_OMNIBOX_OMNIBOX_TEXT_VIEW_H_
