# frozen_string_literal: true

module Openapi3Parser
  module Node
    # This class is used to specify the data and source information for a
    # Node, for every node there is a different context to represent it's
    # place within the document.
    #
    # @attr_reader  [Any]               input             The raw data that was
    #                                                     used to build the
    #                                                     node
    # @attr_reader  [Source::Location]  document_location The location in the
    #                                                     root source of this
    #                                                     node
    # @attr_reader  [Source::Location]  source_location   The location in a
    #                                                     source file of this
    class Context
      # Create a context for the root of a document
      #
      # @param  [NodeFactory::Context]  factory_context
      # @return [Node::Context]
      def self.root(factory_context)
        location = Source::Location.new(factory_context.source, [])
        new(factory_context.input,
            document_location: location,
            source_location: factory_context.source_location)
      end

      # Create a context for the child of a previous context
      #
      # @param  [Node::Context]         parent_context
      # @param  [String]                field
      # @param  [NodeFactory::Context]  factory_context
      # @return [Node::Context]
      def self.next_field(parent_context, field, factory_context)
        document_location = Source::Location.next_field(
          parent_context.document_location,
          field
        )

        new(factory_context.input,
            document_location:,
            source_location: factory_context.source_location)
      end

      # Create a context for a the a field that is the result of a reference
      #
      # @param  [Node::Context]         current_context
      # @param  [NodeFactory::Context]  reference_factory_context
      # @return [Node::Context]
      def self.resolved_reference(current_context, reference_factory_context)
        new(reference_factory_context.input,
            document_location: current_context.document_location,
            source_location: reference_factory_context.source_location)
      end

      attr_reader :input, :document_location, :source_location

      # @param                           input
      # @param  [Source::Location]       document_location
      # @param  [Source::Location]       source_location
      def initialize(input, document_location:, source_location:)
        @input = input
        @document_location = document_location
        @source_location = source_location
      end

      # @param  [Context] other
      # @return [Boolean]
      def ==(other)
        document_location == other.document_location &&
          same_data_and_source?(other)
      end

      # Check that contexts are the same without concern for document location
      #
      # @param  [Context] other
      # @return [Boolean]
      def same_data_and_source?(other)
        input == other.input &&
          source_location == other.source_location
      end

      # The OpenAPI document associated with this context
      #
      # @return [Document]
      def document
        document_location.source.document
      end

      # The source file used to provide the data for this node
      #
      # @return [Source]
      def source
        source_location.source
      end

      # @return [String]
      def inspect
        %{#{self.class.name}(document_location: #{document_location}, } +
          %{source_location: #{source_location})}
      end

      # A string representing the location of the node
      #
      # @return [String]
      def location_summary
        summary = document_location.to_s

        summary += " (#{source_location})" if document_location != source_location

        summary
      end

      # (see #location_summary)
      def to_s
        location_summary
      end

      # Used to return the data at this document location with all references
      # resolved and optional fields populated with defaults
      #
      # @return [Any]
      def resolved_input
        document.resolved_input_at(document_location.pointer)
      end

      # Return the node for this context
      #
      # @return [Node::Object, Node::Map, Node::Array]
      def node
        document.node_at(document_location.pointer)
      end

      # Look up a node at a particular location in the OpenAPI docuemnt based
      # on the relative position in the document of this context
      #
      # Examples:
      #
      # context.relative_node("#schemas")
      # context.relative_node(%w[..])
      #
      # @param [Source::Pointer, String, Array] pointer
      # @return anything
      def relative_node(pointer)
        document.node_at(pointer, document_location.pointer)
      end

      # Return the node that is the parent node for the node at this context
      #
      # @return [Node::Object, Node::Map, Node::Array, nil]
      def parent_node
        return if document_location.root?

        relative_node("#..")
      end
    end
  end
end
