// Copyright 2022 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_BASE_IME_SURROUNDING_TEXT_TRACKER_H_
#define UI_BASE_IME_SURROUNDING_TEXT_TRACKER_H_

#include <deque>
#include <string>
#include <string_view>
#include <variant>

#include "base/component_export.h"
#include "base/functional/callback.h"
#include "ui/base/ime/text_input_client.h"
#include "ui/gfx/range/range.h"

namespace ui {

struct CompositionText;

// Tracks the surrounding text. Because IMF works in an asynchronous manner,
// there will be timing gap that IM-Engine sends some request to the text input
// client, and the commit is actually notified.
// This class gives it a try to fill the gap by caching and emulating the
// latest surrounding text under the assumption that text editing requests are
// processed in a common manner.
class COMPONENT_EXPORT(UI_BASE_IME) SurroundingTextTracker {
 public:
  struct COMPONENT_EXPORT(UI_BASE_IME) State {
    // Returns the range of the surrounding text in UTF-16.
    gfx::Range GetSurroundingTextRange() const;

    // Returns the string piece of the composition range of the
    // |surrounding_text|.
    // If composition is out of the range, nullopt will be returned.
    std::optional<std::u16string_view> GetCompositionText() const;

    // Whole surrounding text, specifically this may include composition text.
    std::u16string surrounding_text;

    // Offset of the surrounding_text within the text input client.
    // This does not affect to either selection nor composition.
    size_t utf16_offset;

    // Selection range. If it is empty, it means the cursor. Must not be
    // InvalidRange. Must be fit in |surrounding_text| range.
    gfx::Range selection;

    // Composition range if it has. Maybe empty if there's no composition text.
    // Must not be InvalidRange. Must be fit in |surrounding_text| range.
    gfx::Range composition;
  };

  // The initial state is no-surrounding text, cursor at 0, and no composition.
  SurroundingTextTracker();
  SurroundingTextTracker(const SurroundingTextTracker&) = delete;
  SurroundingTextTracker& operator=(const SurroundingTextTracker&) = delete;
  ~SurroundingTextTracker();

  const State& predicted_state() const { return predicted_state_; }

  // Resets the internal state, including composition state, surrounding text
  // and held histories. Used when the entire state needs to be reset.
  // TODO(b/267944900): Investigate if this is still needed once
  // kWaylandCancelComposition flag is enabled by default.
  void Reset();

  // Resets only the composition state and held histories.
  // Used when only the composition state is cancelled by the input field.
  void CancelComposition();

  enum class UpdateResult {
    // Expected update entry is found in |expected_updates_|.
    kUpdated,
    // No update entry corresponding to the given arguments is found.
    // All the states are reset to the given arguments.
    kReset,
  };
  // Expected to be called on surrounding text update event from text input
  // client. If there was some known state matching to the arguments,
  // forgets the state histories before it, and returns kUpdateFoundInHistory.
  // Otherwise, forgets everything and reset by the state of the given
  // arguments, then returns kHistoryIsReset.
  // Note intentiontally ignored composition text.
  UpdateResult Update(const std::u16string_view surrounding_text,
                      size_t utf16_offset,
                      const gfx::Range& selection);

  // The following methods are used to guess new surrounding text state.
  // See TextInputClient for detailed behavior.
  void OnSetEditableSelectionRange(const gfx::Range& range);
  void OnSetCompositionText(const ui::CompositionText& composition);
  void OnSetCompositionFromExistingText(const gfx::Range& range);
  void OnConfirmCompositionText(bool keep_selection);
  void OnClearCompositionText();
  void OnInsertText(const std::u16string_view text,
                    TextInputClient::InsertTextCursorBehavior cursor_behavior);
  void OnExtendSelectionAndDelete(size_t before, size_t after);

 private:
  // History of events and their expected states.
  struct Entry {
    State state;
    base::RepeatingClosure command;

    Entry(State state, base::RepeatingClosure command);

    // Copy/Move-able.
    Entry(const Entry&);
    Entry(Entry&& entry);
    Entry& operator=(const Entry&);
    Entry& operator=(Entry&&);

    ~Entry();
  };

  void ResetInternal(std::u16string_view surrounding_text,
                     size_t utf16_offset,
                     const gfx::Range& selection);

  // The latest known state.
  State predicted_state_;
  std::deque<Entry> expected_updates_;
};

}  // namespace ui

#endif  // UI_BASE_IME_SURROUNDING_TEXT_TRACKER_H_
