# frozen_string_literal: true
# :markup: markdown
#--
# rbs_inline: enabled

#--
# Here we are reopening the prism module to provide methods on nodes that aren't
# templated and are meant as convenience methods.
#++
module Prism
  class Node
    #: (*String replacements) -> void
    def deprecated(*replacements) # :nodoc:
      location = caller_locations(1, 1)&.[](0)&.label
      suggest = replacements.map { |replacement| "#{self.class}##{replacement}" }

      warn(<<~MSG, uplevel: 1, category: :deprecated)
        [deprecation]: #{self.class}##{location} is deprecated and will be \
        removed in the next major version. Use #{suggest.join("/")} instead.
        #{(caller(1, 3) || []).join("\n")}
      MSG
    end
  end

  module RegularExpressionOptions # :nodoc:
    # Returns a numeric value that represents the flags that were used to create
    # the regular expression.
    #--
    #: (Integer flags) -> Integer
    def self.options(flags)
      o = 0
      o |= Regexp::IGNORECASE if flags.anybits?(RegularExpressionFlags::IGNORE_CASE)
      o |= Regexp::EXTENDED if flags.anybits?(RegularExpressionFlags::EXTENDED)
      o |= Regexp::MULTILINE if flags.anybits?(RegularExpressionFlags::MULTI_LINE)
      o |= Regexp::FIXEDENCODING if flags.anybits?(RegularExpressionFlags::EUC_JP | RegularExpressionFlags::WINDOWS_31J | RegularExpressionFlags::UTF_8)
      o |= Regexp::NOENCODING if flags.anybits?(RegularExpressionFlags::ASCII_8BIT)
      o
    end
  end

  class InterpolatedMatchLastLineNode < Node
    # Returns a numeric value that represents the flags that were used to create
    # the regular expression.
    #--
    #: () -> Integer
    def options
      RegularExpressionOptions.options(flags)
    end
  end

  class InterpolatedRegularExpressionNode < Node
    # Returns a numeric value that represents the flags that were used to create
    # the regular expression.
    #--
    #: () -> Integer
    def options
      RegularExpressionOptions.options(flags)
    end
  end

  class MatchLastLineNode < Node
    # Returns a numeric value that represents the flags that were used to create
    # the regular expression.
    #--
    #: () -> Integer
    def options
      RegularExpressionOptions.options(flags)
    end
  end

  class RegularExpressionNode < Node
    # Returns a numeric value that represents the flags that were used to create
    # the regular expression.
    #--
    #: () -> Integer
    def options
      RegularExpressionOptions.options(flags)
    end
  end

  private_constant :RegularExpressionOptions

  module HeredocQuery # :nodoc:
    # Returns true if this node was represented as a heredoc in the source code.
    #--
    #: (String? opening) -> bool?
    def self.heredoc?(opening)
      # @type self: InterpolatedStringNode | InterpolatedXStringNode | StringNode | XStringNode
      opening&.start_with?("<<")
    end
  end

  class InterpolatedStringNode < Node
    # Returns true if this node was represented as a heredoc in the source code.
    #--
    #: () -> bool?
    def heredoc?
      HeredocQuery.heredoc?(opening)
    end
  end

  class InterpolatedXStringNode < Node
    # Returns true if this node was represented as a heredoc in the source code.
    #--
    #: () -> bool?
    def heredoc?
      HeredocQuery.heredoc?(opening)
    end
  end

  class StringNode < Node
    # Returns true if this node was represented as a heredoc in the source code.
    #--
    #: () -> bool?
    def heredoc?
      HeredocQuery.heredoc?(opening)
    end

    # Occasionally it's helpful to treat a string as if it were interpolated so
    # that there's a consistent interface for working with strings.
    #--
    #: () -> InterpolatedStringNode
    def to_interpolated
      InterpolatedStringNode.new(
        source,
        -1,
        location,
        frozen? ? InterpolatedStringNodeFlags::FROZEN : 0,
        opening_loc,
        [copy(location: content_loc, opening_loc: nil, closing_loc: nil)],
        closing_loc
      )
    end
  end

  class XStringNode < Node
    # Returns true if this node was represented as a heredoc in the source code.
    #--
    #: () -> bool?
    def heredoc?
      HeredocQuery.heredoc?(opening)
    end

    # Occasionally it's helpful to treat a string as if it were interpolated so
    # that there's a consistent interface for working with strings.
    #--
    #: () -> InterpolatedXStringNode
    def to_interpolated
      InterpolatedXStringNode.new(
        source,
        -1,
        location,
        flags,
        opening_loc,
        [StringNode.new(source, node_id, content_loc, 0, nil, content_loc, nil, unescaped)],
        closing_loc
      )
    end
  end

  private_constant :HeredocQuery

  class ImaginaryNode < Node
    # Returns the value of the node as a Ruby Complex.
    #--
    #: () -> Complex
    def value
      Complex(0, numeric.value)
    end
  end

  class RationalNode < Node
    # Returns the value of the node as a Ruby Rational.
    #--
    #: () -> Rational
    def value
      Rational(numerator, denominator)
    end
  end

  class ConstantReadNode < Node
    # Returns the list of parts for the full name of this constant.
    # For example: [:Foo]
    #--
    #: () -> Array[Symbol]
    def full_name_parts
      [name]
    end

    # Returns the full name of this constant. For example: "Foo"
    #--
    #: () -> String
    def full_name
      name.to_s
    end
  end

  class ConstantWriteNode < Node
    # Returns the list of parts for the full name of this constant.
    # For example: [:Foo]
    #--
    #: () -> Array[Symbol]
    def full_name_parts
      [name]
    end

    # Returns the full name of this constant. For example: "Foo"
    #--
    #: () -> String
    def full_name
      name.to_s
    end
  end

  class ConstantPathNode < Node
    # An error class raised when dynamic parts are found while computing a
    # constant path's full name. For example:
    # Foo::Bar::Baz -> does not raise because all parts of the constant path are
    # simple constants
    # var::Bar::Baz -> raises because the first part of the constant path is a
    # local variable
    class DynamicPartsInConstantPathError < StandardError; end

    # An error class raised when error recovery nodes are found while computing a
    # constant path's full name. For example:
    # Foo:: -> raises because the constant path is missing the last part
    class ErrorRecoveryNodesInConstantPathError < StandardError; end

    # Returns the list of parts for the full name of this constant path.
    # For example: [:Foo, :Bar]
    #--
    #: () -> Array[Symbol]
    def full_name_parts
      parts = [] #: Array[Symbol]
      current = self #: node?

      while current.is_a?(ConstantPathNode)
        name = current.name
        if name.nil?
          raise ErrorRecoveryNodesInConstantPathError, "Constant path contains error recovery nodes. Cannot compute full name"
        end

        parts.unshift(name)
        current = current.parent
      end

      if !current.is_a?(ConstantReadNode) && !current.nil?
        raise DynamicPartsInConstantPathError, "Constant path contains dynamic parts. Cannot compute full name"
      end

      parts.unshift(current&.name || :"")
    end

    # Returns the full name of this constant path. For example: "Foo::Bar"
    #--
    #: () -> String
    def full_name
      full_name_parts.join("::")
    end
  end

  class ConstantPathTargetNode < Node
    # Returns the list of parts for the full name of this constant path.
    # For example: [:Foo, :Bar]
    #--
    #: () -> Array[Symbol]
    def full_name_parts
      parts =
        case (parent = self.parent)
        when ConstantPathNode, ConstantReadNode
          parent.full_name_parts
        when nil
          [:""]
        else
          # e.g. self::Foo, (var)::Bar = baz
          raise ConstantPathNode::DynamicPartsInConstantPathError, "Constant target path contains dynamic parts. Cannot compute full name"
        end

      if (name = self.name).nil?
        raise ConstantPathNode::ErrorRecoveryNodesInConstantPathError, "Constant target path contains error recovery nodes. Cannot compute full name"
      end

      parts.push(name)
    end

    # Returns the full name of this constant path. For example: "Foo::Bar"
    #--
    #: () -> String
    def full_name
      full_name_parts.join("::")
    end
  end

  class ConstantTargetNode < Node
    # Returns the list of parts for the full name of this constant.
    # For example: [:Foo]
    #--
    #: () -> Array[Symbol]
    def full_name_parts
      [name]
    end

    # Returns the full name of this constant. For example: "Foo"
    #--
    #: () -> String
    def full_name
      name.to_s
    end
  end

  class ParametersNode < Node
    # Mirrors the Method#parameters method.
    #--
    #: () -> Array[[Symbol, Symbol] | [Symbol]]
    def signature
      names = [] #: Array[[Symbol, Symbol] | [Symbol]]

      requireds.each do |param|
        names << (param.is_a?(MultiTargetNode) ? [:req] : [:req, param.name])
      end

      optionals.each { |param| names << [:opt, param.name] }

      if (rest = self.rest).is_a?(RestParameterNode)
        names << [:rest, rest.name || :*]
      end

      posts.each do |param|
        case param
        when MultiTargetNode
          names << [:req]
        when ErrorRecoveryNode
          raise "Invalid syntax"
        else
          names << [:req, param.name]
        end
      end

      # Regardless of the order in which the keywords were defined, the required
      # keywords always come first followed by the optional keywords.
      keyopt = [] #: Array[OptionalKeywordParameterNode]
      keywords.each do |param|
        if param.is_a?(OptionalKeywordParameterNode)
          keyopt << param
        else
          names << [:keyreq, param.name]
        end
      end

      keyopt.each { |param| names << [:key, param.name] }

      case (keyword_rest = self.keyword_rest)
      when ForwardingParameterNode
        names.concat([[:rest, :*], [:keyrest, :**], [:block, :&]])
      when KeywordRestParameterNode
        names << [:keyrest, keyword_rest.name || :**]
      when NoKeywordsParameterNode
        names << [:nokey]
      end

      case (block = self.block)
      when BlockParameterNode
        names << [:block, block.name || :&]
      when NoBlockParameterNode
        names << [:noblock]
      end

      names
    end
  end

  class CallNode < Node
    # When a call node has the attribute_write flag set, it means that the call
    # is using the attribute write syntax. This is either a method call to []=
    # or a method call to a method that ends with =. Either way, the = sign is
    # present in the source.
    #
    # Prism returns the message_loc _without_ the = sign attached, because there
    # can be any amount of space between the message and the = sign. However,
    # sometimes you want the location of the full message including the inner
    # space and the = sign. This method provides that.
    #--
    #: () -> Location?
    def full_message_loc
      attribute_write? ? message_loc&.adjoin("=") : message_loc
    end
  end

  # :stopdoc:
  class InNode < Node
    #: () -> String
    def in # :nodoc
      in_keyword
    end

    #: () -> Location
    def in_loc # :nodoc
      in_keyword_loc
    end

    #: () -> String?
    def then # :nodoc
      then_keyword
    end

    #: () -> Location?
    def then_loc # :nodoc
      then_keyword_loc
    end
  end

  class MatchPredicateNode < Node
    #: () -> String
    def operator # :nodoc
      keyword
    end

    #: () -> Location
    def operator_loc # :nodoc
      keyword_loc
    end
  end

  class UnlessNode < Node
    #: () -> String
    def keyword # :nodoc
      unless_keyword
    end

    #: () -> Location
    def keyword_loc # :nodoc
      unless_keyword_loc
    end
  end

  class UntilNode < Node
    #: () -> String
    def keyword # :nodoc
      until_keyword
    end

    #: () -> Location
    def keyword_loc # :nodoc
      until_keyword_loc
    end

    #: () -> String?
    def closing # :nodoc
      end_keyword
    end

    #: () -> Location?
    def closing_loc # :nodoc
      end_keyword_loc
    end
  end

  class WhenNode < Node
    #: () -> String
    def keyword # :nodoc
      when_keyword
    end

    #: () -> Location
    def keyword_loc # :nodoc
      when_keyword_loc
    end
  end

  class WhileNode < Node
    #: () -> String
    def keyword # :nodoc
      while_keyword
    end

    #: () -> Location
    def keyword_loc # :nodoc
      while_keyword_loc
    end

    #: () -> String?
    def closing # :nodoc
      end_keyword
    end

    #: () -> Location?
    def closing_loc # :nodoc
      end_keyword_loc
    end
  end
  # :startdoc:
end
