class Scaffolding::BlockManipulator
  attr_accessor :lines

  def initialize(filepath)
    @filepath = filepath
    @lines = File.readlines(filepath)
  end

  #
  # Wrap a block of ruby code with another block on the outside.
  #
  # @param [String] `starting` A string to search for at the start of the block. Eg "<%= updates_for context, collection do"
  # @param [Array] `with` An array with two String elements. The text that should wrap the block. Eg ["<%= action_model_select_controller do %>", "<% end %>"]
  #
  def wrap_block(starting:, with:)
    with[0] += "\n" unless with[0].match?(/\n$/)
    with[1] += "\n" unless with[1].match?(/\n$/)
    starting_line = find_block_start(starting)
    end_line = find_block_end(starting_from: starting_line, lines: @lines)

    final = []
    block_indent = ""
    spacer = "  "
    @lines.each_with_index do |line, index|
      line += "\n" unless line.match?(/\n$/)
      if index < starting_line
        final << line
      elsif index == starting_line
        block_indent = line.match(/^\s*/).to_s
        final << block_indent + with[0]
        final << (line.blank? ? "\n" : "#{spacer}#{line}")
      elsif index > starting_line && index < end_line
        final << (line.blank? ? "\n" : "#{spacer}#{line}")
      elsif index == end_line
        final << (line.blank? ? "\n" : "#{spacer}#{line}")
        final << block_indent + with[1]
      else
        final << line
      end
    end

    @lines = final
    unless @lines.last.match?(/\n$/)
      @lines.last += "\n"
    end
  end

  def insert(content, within: nil, after: nil, before: nil, after_block: nil, append: false)
    # Search for before like we do after, we'll just inject before it.
    after ||= before

    # If within is given, find the start and end lines of the block
    content += "\n" unless content.match?(/\n$/)
    start_line = 0
    end_line = @lines.count - 1
    if within.present?
      start_line = find_block_start(within)
      end_line = find_block_end(starting_from: start_line, lines: @lines)
      # start_line += 1 # ensure we actually insert the content _within_ the given block
      # end_line += 1 if end_line == start_line
    end
    if after_block.present?
      block_start = find_block_start(after_block)
      block_end = find_block_end(starting_from: block_start, lines: @lines)
      start_line = block_end
      end_line = @lines.count - 1
    end
    index = start_line
    match = false
    while index < end_line && !match
      line = @lines[index]
      if after.nil? || line.match?(after)
        unless append
          match = true
          # We adjust the injection point if we really wanted to insert before.
          insert_line(content, index - (before ? 1 : 0))
        end
      end
      index += 1
    end

    return if match

    # Match should always be false here.
    if append && !match
      insert_line(content, index - 1)
    end
  end

  def insert_line(content, insert_at_index)
    content += "\n" unless content.match?(/\n$/)
    final = []
    @lines.each_with_index do |line, index|
      indent = line.match(/^\s*/).to_s
      final << line
      if index == insert_at_index
        final << indent + content
      end
    end
    @lines = final
  end

  def insert_block(block_content, after_block:)
    block_start = find_block_start(after_block)
    block_end = find_block_end(starting_from: block_start, lines: @lines)
    insert_line(block_content[0], block_end)
    insert_line(block_content[1], block_end + 1)
  end

  # TODO: Delete this because it only really needs to be in the FileManipulator
  def write
    File.write(@filepath, @lines.join)
  end

  def find_block_parent(starting_line_number, lines)
    return nil unless indentation_of(starting_line_number, lines)
    cursor = starting_line_number
    while cursor >= 0
      unless lines[cursor].match?(/^#{indentation_of(starting_line_number, lines)}/) || !lines[cursor].present?
        return cursor
      end
      cursor -= 1
    end
    nil
  end

  def find_block_start(starting_string)
    matcher = Regexp.escape(starting_string)
    starting_line = 0

    @lines.each_with_index do |line, index|
      if line.match?(matcher)
        starting_line = index
        break
      end
    end
    starting_line
  end

  def find_block_end(starting_from:, lines:)
    # This loop was previously in the RoutesFileManipulator.
    lines.each_with_index do |line, line_number|
      next unless line_number > starting_from
      if /^#{indentation_of(starting_from, lines)}end\s+/.match?(line)
        return line_number
      end
    end

    depth = 0
    current_line = starting_from
    @lines[starting_from..@lines.count].each_with_index do |line, index|
      current_line = starting_from + index
      depth += 1 if line.match?(/\s*<%.+ do .*%>/)
      depth += 1 if line.match?(/\s*<% if .*%>/)
      depth += 1 if line.match?(/\s*<% unless .*%>/)
      depth -= 1 if line.match?(/\s*<%.* end .*%>/)
      break current_line if depth == 0
    end
    current_line
  end

  # TODO: We shouldn't need this second argument, but since
  # we have `lines` here and in the RoutesFileManipulator,
  # the lines diverge from one another when we edit them individually.
  def indentation_of(line_number, lines)
    lines[line_number].match(/^( +)/)[1]
  rescue
    nil
  end
end