class Gherkin::AstBuilder

def build(token)

def build(token)
  if token.matched_type == :Comment
    @comments.push(
      Cucumber::Messages::Comment.new(
        location: get_location(token, 0),
        text: token.matched_text
      )
    )
  else
    current_node.add(token.matched_type, token)
  end
end

def current_node

def current_node
  @stack.last
end

def end_rule(_rule_type)

def end_rule(_rule_type)
  node = @stack.pop
  current_node.add(node.rule_type, transform_node(node))
end

def ensure_cell_count(rows)

def ensure_cell_count(rows)
  return if rows.empty?
  cell_count = rows[0].cells.length
  rows.each do |row|
    raise AstBuilderException.new('inconsistent cell count within the table', row.location.to_h) if row.cells.length != cell_count
  end
end

def get_cells(table_row_token)

def get_cells(table_row_token)
  table_row_token.matched_items.map do |cell_item|
    Cucumber::Messages::TableCell.new(
      location: get_location(table_row_token, cell_item.column),
      value: cell_item.text
    )
  end
end

def get_description(node)

def get_description(node)
  node.get_single(:Description) || ''
end

def get_location(token, column)

def get_location(token, column)
  column = column == 0 ? token.location[:column] : column
  Cucumber::Messages::Location.new(
    line: token.location[:line],
    column: column
  )
end

def get_result

def get_result
  current_node.get_single(:GherkinDocument)
end

def get_steps(node)

def get_steps(node)
  node.get_items(:Step)
end

def get_table_rows(node)

def get_table_rows(node)
  rows = node.get_tokens(:TableRow).map do |token|
    Cucumber::Messages::TableRow.new(
      id: @id_generator.new_id,
      location: get_location(token, 0),
      cells: get_cells(token)
    )
  end
  rows.tap { ensure_cell_count(rows) }
end

def get_tags(node)

def get_tags(node)
  tags = []
  tags_node = node.get_single(:Tags)
  return tags unless tags_node
  tags_node.get_tokens(:TagLine).each do |token|
    token.matched_items.each do |tag_item|
      tags.push(
        Cucumber::Messages::Tag.new(
          location: get_location(token, tag_item.column),
          name: tag_item.text,
          id: @id_generator.new_id
        )
      )
    end
  end
  tags
end

def initialize(id_generator)

def initialize(id_generator)
  @id_generator = id_generator
  reset
end

def reset

def reset
  @stack = [AstNode.new(:None)]
  @comments = []
end

def start_rule(rule_type)

def start_rule(rule_type)
  @stack.push AstNode.new(rule_type)
end

def transform_node(node)

def transform_node(node)
  case node.rule_type
  when :Step
    step_line = node.get_token(:StepLine)
    data_table = node.get_single(:DataTable)
    doc_string = node.get_single(:DocString)
    Cucumber::Messages::Step.new(
      location: get_location(step_line, 0),
      keyword: step_line.matched_keyword,
      keyword_type: step_line.matched_keyword_type,
      text: step_line.matched_text,
      data_table: data_table,
      doc_string: doc_string,
      id: @id_generator.new_id
    )
  when :DocString
    separator_token = node.get_tokens(:DocStringSeparator)[0]
    media_type = separator_token.matched_text == '' ? nil : separator_token.matched_text
    line_tokens = node.get_tokens(:Other)
    content = line_tokens.map { |t| t.matched_text }.join("\n")
    Cucumber::Messages::DocString.new(
      location: get_location(separator_token, 0),
      content: content,
      delimiter: separator_token.matched_keyword,
      media_type: media_type
    )
  when :DataTable
    rows = get_table_rows(node)
    Cucumber::Messages::DataTable.new(
      location: rows[0].location,
      rows: rows
    )
  when :Background
    background_line = node.get_token(:BackgroundLine)
    description = get_description(node)
    steps = get_steps(node)
    Cucumber::Messages::Background.new(
      id: @id_generator.new_id,
      location: get_location(background_line, 0),
      keyword: background_line.matched_keyword,
      name: background_line.matched_text,
      description: description,
      steps: steps
    )
  when :ScenarioDefinition
    tags = get_tags(node)
    scenario_node = node.get_single(:Scenario)
    scenario_line = scenario_node.get_token(:ScenarioLine)
    description = get_description(scenario_node)
    steps = get_steps(scenario_node)
    examples = scenario_node.get_items(:ExamplesDefinition)
    Cucumber::Messages::Scenario.new(
      id: @id_generator.new_id,
      tags: tags,
      location: get_location(scenario_line, 0),
      keyword: scenario_line.matched_keyword,
      name: scenario_line.matched_text,
      description: description,
      steps: steps,
      examples: examples
    )
  when :ExamplesDefinition
    tags = get_tags(node)
    examples_node = node.get_single(:Examples)
    examples_line = examples_node.get_token(:ExamplesLine)
    description = get_description(examples_node)
    rows = examples_node.get_single(:ExamplesTable)
    table_header = rows.nil? ? nil : rows.first
    table_body = rows.nil? ? [] : rows[1..-1]
    Cucumber::Messages::Examples.new(
      id: @id_generator.new_id,
      tags: tags,
      location: get_location(examples_line, 0),
      keyword: examples_line.matched_keyword,
      name: examples_line.matched_text,
      description: description,
      table_header: table_header,
      table_body: table_body
    )
  when :ExamplesTable
    get_table_rows(node)
  when :Description
    line_tokens = node.get_tokens(:Other)
    # Trim trailing empty lines
    last_non_empty = line_tokens.rindex { |token| !token.line.trimmed_line_text.empty? }
    line_tokens[0..last_non_empty].map { |token| token.matched_text }.join("\n")
  when :Feature
    header = node.get_single(:FeatureHeader)
    return unless header
    tags = get_tags(header)
    feature_line = header.get_token(:FeatureLine)
    return unless feature_line
    children = []
    background = node.get_single(:Background)
    children.push(Cucumber::Messages::FeatureChild.new(background: background)) if background
    node.get_items(:ScenarioDefinition).each do |scenario|
      children.push(Cucumber::Messages::FeatureChild.new(scenario: scenario))
    end
    node.get_items(:Rule).each do |rule|
      children.push(Cucumber::Messages::FeatureChild.new(rule: rule))
    end
    description = get_description(header)
    language = feature_line.matched_gherkin_dialect
    Cucumber::Messages::Feature.new(
      tags: tags,
      location: get_location(feature_line, 0),
      language: language,
      keyword: feature_line.matched_keyword,
      name: feature_line.matched_text,
      description: description,
      children: children,
    )
  when :Rule
    header = node.get_single(:RuleHeader)
    return unless header
    rule_line = header.get_token(:RuleLine)
    return unless rule_line
    tags = get_tags(header)
    children = []
    background = node.get_single(:Background)
    children.push(Cucumber::Messages::RuleChild.new(background: background)) if background
    node.get_items(:ScenarioDefinition).each do |scenario|
      children.push(Cucumber::Messages::RuleChild.new(scenario: scenario))
    end
    description = get_description(header)
    Cucumber::Messages::Rule.new(
      id: @id_generator.new_id,
      tags: tags,
      location: get_location(rule_line, 0),
      keyword: rule_line.matched_keyword,
      name: rule_line.matched_text,
      description: description,
      children: children,
    )
  when :GherkinDocument
    feature = node.get_single(:Feature)
    Cucumber::Messages::GherkinDocument.new(
      comments: @comments,
      feature: feature
    )
  else
    node
  end
end