struct RangeWithMarkup
  getter range : Range(Int32, Int32)
  getter markups : Array(PostResponse::Markup)

  def initialize(@range : Range, @markups : Array(PostResponse::Markup))
  end
end

class MarkupConverter
  include Nodes

  getter markups : Array(PostResponse::Markup)
  getter text : Slice(UInt16)

  def self.convert(text : String?, markups : Array(PostResponse::Markup))
    new(text, markups).convert
  end

  def initialize(text : String?, @markups : Array(PostResponse::Markup))
    @text = (text || "").to_utf16
  end

  def convert : Array(Child)
    ranges.flat_map do |range_with_markups|
      text_to_wrap = String.from_utf16(text[range_with_markups.range])
      wrap_in_markups(text_to_wrap, range_with_markups.markups)
    end
  end

  private def ranges
    markup_boundaries = markups.flat_map { |markup| [markup.start, markup.end] }
    bookended_markup_boundaries = ([0] + markup_boundaries + [text.size]).uniq.sort
    bookended_markup_boundaries.each_cons(2).map do |boundaries|
      range = (boundaries[0]...boundaries[1])
      covered_markups = markups.select do |markup|
        range.covers?(markup.start) || range.covers?(markup.end - 1)
      end
      RangeWithMarkup.new(range, covered_markups)
    end.to_a
  end

  def wrap_in_markups(
    child : String | Child,
    markups : Array(PostResponse::Markup)
  ) : Array(Child)
    if child.is_a?(String)
      child = Text.new(child)
    end
    if markups.first?.nil?
      return [child] of Child
    end
    marked_up = markup_node_in_container(child, markups[0])
    wrap_in_markups(marked_up, markups[1..])
  end

  private def markup_node_in_container(child : Child, markup : PostResponse::Markup)
    case markup.type
    when PostResponse::MarkupType::A
      if href = markup.href
        Anchor.new(href: href, children: [child] of Child)
      elsif user_id = markup.userId
        UserAnchor.new(user_id: user_id, children: [child] of Child)
      else
        Empty.new
      end
    when PostResponse::MarkupType::CODE
      Code.new(children: [child] of Child)
    when PostResponse::MarkupType::EM
      Emphasis.new(children: [child] of Child)
    when PostResponse::MarkupType::STRONG
      Strong.new(children: [child] of Child)
    else
      Code.new(children: [child] of Child)
    end
  end
end
