module Gherkin
  class Query
    def initialize
      @ast_node_locations = {}
      @scenario_parent_locations = {}
      @background_locations = {}
    end

    def update(message)
      update_feature(message.gherkin_document.feature) if message.gherkin_document
    end

    def scenario_parent_locations(scenario_node_id)
      return @scenario_parent_locations[scenario_node_id] if @scenario_parent_locations.has_key?(scenario_node_id)
      raise AstNodeNotLocatedException, "No scenario parent locations found for #{scenario_node_id} }. Known: #{@scenario_parent_locations.keys}"
    end

    def location(ast_node_id)
      return @ast_node_locations[ast_node_id] if @ast_node_locations.has_key?(ast_node_id)
      raise AstNodeNotLocatedException, "No location found for #{ast_node_id} }. Known: #{@ast_node_locations.keys}"
    end

    private

    def update_feature(feature)
      return if feature.nil?
      store_nodes_location(feature.tags)

      feature.children.each do |child|
        update_rule(feature, child.rule) if child.rule
        update_background(feature, child.background) if child.background
        update_scenario(feature, child.rule, child.scenario) if child.scenario
      end
    end

    def update_rule(feature, rule)
      return if rule.nil?
      store_nodes_location(rule.tags)
      rule.children.each do |child|
        update_background(rule, child.background) if child.background
        update_scenario(feature, rule, child.scenario) if child.scenario
      end
    end

    def update_background(parent, background)
      update_steps(background.steps)
      @background_locations[parent] = background.location
    end

    def update_scenario(feature, rule, scenario)
      store_node_location(scenario)
      store_nodes_location(scenario.tags)
      update_steps(scenario.steps)
      scenario.examples.each do |examples|
        store_nodes_location(examples.tags || [])
        store_nodes_location(examples.table_body || [])
      end

      @scenario_parent_locations[scenario.id] = [
        feature.location,
        @background_locations[feature],
        rule&.location,
        @background_locations[rule],
      ].compact
    end

    def update_steps(steps)
      store_nodes_location(steps)
    end

    def store_nodes_location(nodes)
      nodes.each { |node| store_node_location(node) }
    end

    def store_node_location(node)
      @ast_node_locations[node.id] = node.location
    end
  end
end
