#pragma once

#include "xtr/viewmodel/vmTextEdit.h"

#include <set>

class MockTextEditCtrl : public xtr::viewmodel::ITextEdit {
public:
  struct rangeComparator {
    constexpr bool operator()(const xtr::viewmodel::HighlightItem& rng1,
                              const xtr::viewmodel::HighlightItem& rng2) const noexcept {
      return rng1.rng.start < rng2.rng.start;
    }
  };

public:
  CString text{};
  std::set<xtr::viewmodel::HighlightItem, rangeComparator> items{};

private:
  [[nodiscard]] CString GetText() const override {
    return text;
  }

  void SetText(const CString newText) override {
    text = newText;
    items.clear();
  }

  void ReplaceRange(const StrRange range, const CString replacement) override {
    CString newText{};
    if (range.start > 0) {
      newText.Append(text.Left(range.start - 1));
    }
    newText.Append(replacement);
    if (range.finish < text.GetLength()) {
      newText.Append(text.Right(text.GetLength() - range.finish));
    }
    text = newText;

    std::set<xtr::viewmodel::HighlightItem, rangeComparator> newItems{};
    const auto shift = replacement.GetLength() - range.length();
    for (auto refIt = begin(items); refIt != end(items); ++refIt) {
      const auto& refPosition = refIt->rng;
      if (refPosition.Meets(range) || refPosition.IsBefore(range)) {
        newItems.emplace(*refIt);
      } else if (range.Meets(refPosition) || refPosition.IsAfter(range)) {
        auto newRef = *refIt;
        newRef.rng.Shift(shift);
        newItems.emplace(newRef);
      } else if (range.Contains(refPosition)) {
        continue;
      } else {
        auto newRef = *refIt;
        if (refPosition.start < range.start) {
          newRef.rng.SetLength(refPosition.length() - refPosition.Intersect(range)->length());
        } else {
          newRef.rng = StrRange::FromLength(range.start, std::max(0,
                                                                  refPosition.length() - refPosition.Intersect(range)->length()));
        }
        newItems.emplace(newRef);
      }
    }
    items = newItems;
  }

  void Highlight(xtr::viewmodel::HighlightItem item) override {
    items.insert(item);
  }

  [[nodiscard]] std::optional<xtr::viewmodel::HighlightItem> NextHighlighted(const StrPos start) override {
    if (start == 0) {
      if (empty(items)) {
        return std::nullopt;
      } else {
        return *begin(items);
      }
    }

    if (auto it = std::find_if(begin(items), end(items),
                               [&](const xtr::viewmodel::HighlightItem& item) { return item.rng.start == start; });
        it == end(items)) {
      return std::nullopt;
    } else if (++it; it == end(items)) {
      return std::nullopt;
    } else {
      return *it;
    }
  }
};