module TypeProf::Core
  class AST
    def self.parse_rb(path, src)
      result = Prism.parse(src)

      return nil unless result.errors.empty?

      # comments, errors, magic_comments
      raw_scope = result.value

      raise unless raw_scope.type == :program_node

      Fiber[:comments] = result.comments

      cref = CRef::Toplevel
      lenv = LocalEnv.new(path, cref, {}, [])

      ProgramNode.new(raw_scope, lenv)
    end

    #: (untyped, TypeProf::Core::LocalEnv, ?bool) -> TypeProf::Core::AST::Node
    def self.create_node(raw_node, lenv, use_result = true)
      while true
        case raw_node.type
        when :parentheses_node
          raw_node = raw_node.body
        when :implicit_node
          raw_node = raw_node.value
        else
          break
        end
      end

      case raw_node.type

      # definition
      when :statements_node then StatementsNode.new(raw_node, lenv, use_result)
      when :module_node then ModuleNode.new(raw_node, lenv, use_result)
      when :class_node then ClassNode.new(raw_node, lenv, use_result)
      when :singleton_class_node then SingletonClassNode.new(raw_node, lenv, use_result)
      when :def_node then DefNode.new(raw_node, lenv, use_result)
      when :alias_method_node then AliasNode.new(raw_node, lenv)
      when :undef_node then UndefNode.new(raw_node, lenv)

      # control
      when :and_node then AndNode.new(raw_node, lenv)
      when :or_node then OrNode.new(raw_node, lenv)
      when :if_node then IfNode.new(raw_node, lenv)
      when :unless_node then UnlessNode.new(raw_node, lenv)
      when :case_node then CaseNode.new(raw_node, lenv)
      when :case_match_node then CaseMatchNode.new(raw_node, lenv)
      when :while_node then WhileNode.new(raw_node, lenv)
      when :until_node then UntilNode.new(raw_node, lenv)
      when :break_node then BreakNode.new(raw_node, lenv)
      when :next_node then NextNode.new(raw_node, lenv)
      when :redo_node then RedoNode.new(raw_node, lenv)
      when :return_node then ReturnNode.new(raw_node, lenv)
      when :begin_node then BeginNode.new(raw_node, lenv)
      when :retry_node then RetryNode.new(raw_node, lenv)
      when :rescue_modifier_node then RescueModifierNode.new(raw_node, lenv)

      # constants
      when :constant_read_node, :constant_path_node
        ConstantReadNode.new(raw_node, lenv)
      when :constant_write_node, :constant_path_write_node
        ConstantWriteNode.new(raw_node, AST.create_node(raw_node.value, lenv), lenv)
      when :constant_operator_write_node
        read = ConstantReadNode.new(raw_node, lenv)
        rhs = OperatorNode.new(raw_node, read, lenv)
        ConstantWriteNode.new(raw_node, rhs, lenv)
      when :constant_or_write_node
        read = ConstantReadNode.new(raw_node, lenv)
        rhs = OrNode.new(raw_node, read, raw_node.value, lenv)
        ConstantWriteNode.new(raw_node, rhs, lenv)
      when :constant_and_write_node
        read = ConstantReadNode.new(raw_node, lenv)
        rhs = AndNode.new(raw_node, read, raw_node.value, lenv)
        ConstantWriteNode.new(raw_node, rhs, lenv)
      when :constant_path_operator_write_node
        read = ConstantReadNode.new(raw_node.target, lenv)
        rhs = OperatorNode.new(raw_node, read, lenv)
        ConstantWriteNode.new(raw_node, rhs, lenv)
      when :constant_path_or_write_node
        read = ConstantReadNode.new(raw_node.target, lenv)
        rhs = OrNode.new(raw_node, read, raw_node.value, lenv)
        ConstantWriteNode.new(raw_node, rhs, lenv)
      when :constant_path_and_write_node
        read = ConstantReadNode.new(raw_node.target, lenv)
        rhs = AndNode.new(raw_node, read, raw_node.value, lenv)
        ConstantWriteNode.new(raw_node, rhs, lenv)

      # variables
      when :local_variable_read_node
        LocalVariableReadNode.new(raw_node, lenv)
      when :local_variable_write_node
        LocalVariableWriteNode.new(raw_node, AST.create_node(raw_node.value, lenv), lenv)
      when :local_variable_operator_write_node
        read = LocalVariableReadNode.new(raw_node, lenv)
        rhs = OperatorNode.new(raw_node, read, lenv)
        LocalVariableWriteNode.new(raw_node, rhs, lenv)
      when :local_variable_or_write_node
        read = LocalVariableReadNode.new(raw_node, lenv)
        rhs = OrNode.new(raw_node, read, raw_node.value, lenv)
        LocalVariableWriteNode.new(raw_node, rhs, lenv)
      when :local_variable_and_write_node
        read = LocalVariableReadNode.new(raw_node, lenv)
        rhs = AndNode.new(raw_node, read, raw_node.value, lenv)
        LocalVariableWriteNode.new(raw_node, rhs, lenv)
      when :instance_variable_read_node
        InstanceVariableReadNode.new(raw_node, lenv)
      when :instance_variable_write_node
        InstanceVariableWriteNode.new(raw_node, AST.create_node(raw_node.value, lenv), lenv)
      when :instance_variable_operator_write_node
        read = InstanceVariableReadNode.new(raw_node, lenv)
        rhs = OperatorNode.new(raw_node, read, lenv)
        InstanceVariableWriteNode.new(raw_node, rhs, lenv)
      when :instance_variable_or_write_node
        read = InstanceVariableReadNode.new(raw_node, lenv)
        rhs = OrNode.new(raw_node, read, raw_node.value, lenv)
        InstanceVariableWriteNode.new(raw_node, rhs, lenv)
      when :instance_variable_and_write_node
        read = InstanceVariableReadNode.new(raw_node, lenv)
        rhs = AndNode.new(raw_node, read, raw_node.value, lenv)
        InstanceVariableWriteNode.new(raw_node, rhs, lenv)
      when :class_variable_read_node
        ClassVariableReadNode.new(raw_node, lenv)
      when :class_variable_write_node
        ClassVariableWriteNode.new(raw_node, AST.create_node(raw_node.value, lenv), lenv)
      when :class_variable_operator_write_node
        read = ClassVariableReadNode.new(raw_node, lenv)
        rhs = OperatorNode.new(raw_node, read, lenv)
      when :class_variable_or_write_node
        read = ClassVariableReadNode.new(raw_node, lenv)
        rhs = OrNode.new(raw_node, read, raw_node.value, lenv)
        ClassVariableWriteNode.new(raw_node, rhs, lenv)
      when :class_variable_and_write_node
        read = ClassVariableReadNode.new(raw_node, lenv)
        rhs = AndNode.new(raw_node, read, raw_node.value, lenv)
        ClassVariableWriteNode.new(raw_node, rhs, lenv)
      when :global_variable_read_node
        GlobalVariableReadNode.new(raw_node, lenv)
      when :global_variable_write_node
        GlobalVariableWriteNode.new(raw_node, AST.create_node(raw_node.value, lenv), lenv)
      when :global_variable_operator_write_node
        read = GlobalVariableReadNode.new(raw_node, lenv)
        rhs = OperatorNode.new(raw_node, read, lenv)
        GlobalVariableWriteNode.new(raw_node, rhs, lenv)
      when :global_variable_or_write_node
        read = GlobalVariableReadNode.new(raw_node, lenv)
        rhs = OrNode.new(raw_node, read, raw_node.value, lenv)
        GlobalVariableWriteNode.new(raw_node, rhs, lenv)
      when :global_variable_and_write_node
        read = GlobalVariableReadNode.new(raw_node, lenv)
        rhs = AndNode.new(raw_node, read, raw_node.value, lenv)
        GlobalVariableWriteNode.new(raw_node, rhs, lenv)
      when :numbered_reference_read_node
        RegexpReferenceReadNode.new(raw_node, lenv)
      when :back_reference_read_node
        RegexpReferenceReadNode.new(raw_node, lenv)

      # assignment targets
      when :index_operator_write_node
        read = IndexReadNode.new(raw_node, lenv)
        rhs = OperatorNode.new(raw_node, read, lenv)
        IndexWriteNode.new(raw_node, rhs, lenv)
      when :index_or_write_node
        read = IndexReadNode.new(raw_node, lenv)
        rhs = OrNode.new(raw_node, read, raw_node.value, lenv)
        IndexWriteNode.new(raw_node, rhs, lenv)
      when :index_and_write_node
        read = IndexReadNode.new(raw_node, lenv)
        rhs = AndNode.new(raw_node, read, raw_node.value, lenv)
        IndexWriteNode.new(raw_node, rhs, lenv)
      when :call_operator_write_node
        read = CallReadNode.new(raw_node, lenv)
        rhs = OperatorNode.new(raw_node, read, lenv)
        CallWriteNode.new(raw_node, rhs, lenv)
      when :call_or_write_node
        read = CallReadNode.new(raw_node, lenv)
        rhs = OrNode.new(raw_node, read, raw_node.value, lenv)
        CallWriteNode.new(raw_node, rhs, lenv)
      when :call_and_write_node
        read = CallReadNode.new(raw_node, lenv)
        rhs = AndNode.new(raw_node, read, raw_node.value, lenv)
        CallWriteNode.new(raw_node, rhs, lenv)
      when :multi_write_node then MultiWriteNode.new(raw_node, lenv)
      when :match_write_node then MatchWriteNode.new(raw_node, lenv)

      # value
      when :self_node then SelfNode.new(raw_node, lenv)
      when :nil_node then NilNode.new(raw_node, lenv)
      when :true_node then TrueNode.new(raw_node, lenv)
      when :false_node then FalseNode.new(raw_node, lenv)
      when :integer_node then IntegerNode.new(raw_node, lenv)
      when :float_node then FloatNode.new(raw_node, lenv)
      when :rational_node then RationalNode.new(raw_node, lenv)
      when :imaginary_node then ComplexNode.new(raw_node, lenv)
      when :source_file_node then StringNode.new(raw_node, lenv, "")
      when :source_line_node then IntegerNode.new(raw_node, lenv, 0)
      when :source_encoding_node then SourceEncodingNode.new(raw_node, lenv)
      when :symbol_node then SymbolNode.new(raw_node, lenv)
      when :interpolated_symbol_node then InterpolatedSymbolNode.new(raw_node, lenv)
      when :string_node then StringNode.new(raw_node, lenv, raw_node.content)
      when :interpolated_string_node then InterpolatedStringNode.new(raw_node, lenv)
      when :x_string_node then StringNode.new(raw_node, lenv, "")
      when :interpolated_x_string_node then InterpolatedStringNode.new(raw_node, lenv)
      when :regular_expression_node then RegexpNode.new(raw_node, lenv)
      when :interpolated_regular_expression_node then InterpolatedRegexpNode.new(raw_node, lenv)
      when :match_last_line_node then MatchLastLineNode.new(raw_node, lenv)
      when :interpolated_match_last_line_node then InterpolatedMatchLastLineNode.new(raw_node, lenv)
      when :range_node then RangeNode.new(raw_node, lenv)
      when :array_node then ArrayNode.new(raw_node, lenv)
      when :hash_node then HashNode.new(raw_node, lenv, false)
      when :keyword_hash_node then HashNode.new(raw_node, lenv, true)
      when :lambda_node then LambdaNode.new(raw_node, lenv)

      # misc
      when :defined_node then DefinedNode.new(raw_node, lenv)
      when :splat_node then SplatNode.new(raw_node, lenv)
      when :for_node then ForNode.new(raw_node, lenv)
      when :alias_global_variable_node then AliasGlobalVariableNode.new(raw_node, lenv)
      when :post_execution_node then PostExecutionNode.new(raw_node, lenv)
      when :flip_flop_node then FlipFlopNode.new(raw_node, lenv)
      when :shareable_constant_node then create_node(raw_node.write, lenv)
      when :match_required_node then MatchRequiredNode.new(raw_node, lenv)
      when :match_predicate_node then MatchPreidcateNode.new(raw_node, lenv)

      # call
      when :super_node then SuperNode.new(raw_node, lenv)
      when :forwarding_super_node then ForwardingSuperNode.new(raw_node, lenv)
      when :yield_node then YieldNode.new(raw_node, lenv)
      when :call_node
        if !raw_node.receiver
          # TODO: handle them only when it is directly under class or module
          case raw_node.name
          when :include
            return IncludeMetaNode.new(raw_node, lenv)
          when :attr_reader
            return AttrReaderMetaNode.new(raw_node, lenv)
          when :attr_accessor
            return AttrAccessorMetaNode.new(raw_node, lenv)
          end
        end
        CallNode.new(raw_node, lenv)
      else
        pp raw_node
        raise "not supported yet: #{ raw_node.type }"
      end
    end

    def self.create_target_node(raw_node, lenv)
      dummy_node = DummyRHSNode.new(TypeProf::CodeRange.from_node(raw_node.location), lenv)
      case raw_node.type
      when :local_variable_target_node
        LocalVariableWriteNode.new(raw_node, dummy_node, lenv)
      when :instance_variable_target_node
        InstanceVariableWriteNode.new(raw_node, dummy_node, lenv)
      when :class_variable_target_node
        ClassVariableWriteNode.new(raw_node, dummy_node, lenv)
      when :global_variable_target_node
        GlobalVariableWriteNode.new(raw_node, dummy_node, lenv)
      when :constant_target_node
        ConstantWriteNode.new(raw_node, dummy_node, lenv)
      when :constant_path_target_node
        ConstantWriteNode.new(raw_node, dummy_node, lenv)
      when :index_target_node
        IndexWriteNode.new(raw_node, dummy_node, lenv)
      when :call_target_node
        CallWriteNode.new(raw_node, dummy_node, lenv)
      else
        pp raw_node
        raise "not supported yet: #{ raw_node.type }"
      end
    end

    def self.create_pattern_node(raw_node, lenv)
      while true
        case raw_node.type
        when :parentheses_node
          raw_node = raw_node.body
        when :implicit_node
          raw_node = raw_node.value
        else
          break
        end
      end

      case raw_node.type
      when :array_pattern_node then ArrayPatternNode.new(raw_node, lenv)
      when :hash_pattern_node then HashPatternNode.new(raw_node, lenv)
      when :find_pattern_node then FindPatternNode.new(raw_node, lenv)

      when :alternation_pattern_node then AltPatternNode.new(raw_node, lenv)

      when :capture_pattern_node then CapturePatternNode.new(raw_node, lenv)

      when :if_node then IfPatternNode.new(raw_node, lenv)

      when :pinned_variable_node then PinnedPatternNode.new(raw_node, lenv)
      when :pinned_expression_node then PinnedPatternNode.new(raw_node, lenv)

      when :local_variable_target_node
        dummy_node = DummyRHSNode.new(TypeProf::CodeRange.from_node(raw_node.location), lenv)
        LocalVariableWriteNode.new(raw_node, dummy_node, lenv)

      when :constant_read_node, :constant_path_node
        ConstantReadNode.new(raw_node, lenv)

      when :self_node then SelfNode.new(raw_node, lenv)
      when :nil_node then NilNode.new(raw_node, lenv)
      when :true_node then TrueNode.new(raw_node, lenv)
      when :false_node then FalseNode.new(raw_node, lenv)
      when :integer_node then IntegerNode.new(raw_node, lenv)
      when :float_node then FloatNode.new(raw_node, lenv)
      when :rational_node then RationalNode.new(raw_node, lenv)
      when :imaginary_node then ComplexNode.new(raw_node, lenv)
      when :source_file_node then StringNode.new(raw_node, lenv, "")
      when :source_line_node then IntegerNode.new(raw_node, lenv, 0)
      when :source_encoding_node then SourceEncodingNode.new(raw_node, lenv)
      when :symbol_node then SymbolNode.new(raw_node, lenv)
      when :interpolated_symbol_node then InterpolatedSymbolNode.new(raw_node, lenv)
      when :string_node then StringNode.new(raw_node, lenv, raw_node.content)
      when :interpolated_string_node then InterpolatedStringNode.new(raw_node, lenv)
      when :x_string_node then StringNode.new(raw_node, lenv, "")
      when :interpolated_x_string_node then InterpolatedStringNode.new(raw_node, lenv)
      when :regular_expression_node then RegexpNode.new(raw_node, lenv)
      when :interpolated_regular_expression_node then InterpolatedRegexpNode.new(raw_node, lenv)

      when :array_node then ArrayNode.new(raw_node, lenv) # for %w[foo bar]
      when :range_node then RangeNode.new(raw_node, lenv) # TODO: support range pattern correctly

      else
        raise "unknown pattern node type: #{ raw_node.type }"
      end
    end

    def self.parse_cpath(raw_node, cref)
      names = []
      while raw_node
        case raw_node.type
        when :constant_read_node
          names << raw_node.name
          break
        when :constant_path_node, :constant_path_target_node
          if raw_node.parent
            # temporarily support old Prism https://bugs.ruby-lang.org/issues/20467
            names << (raw_node.respond_to?(:name) ? raw_node.name : raw_node.child.name)
            raw_node = raw_node.parent
          else
            return names.reverse
          end
        when :self_node
          break if cref.scope_level == :class
          return nil
        else
          return nil
        end
      end
      return cref.cpath + names.reverse
    end

    def self.parse_rbs(path, src)
      _buffer, _directives, raw_decls = RBS::Parser.parse_signature(src)

      cref = CRef::Toplevel
      lenv = LocalEnv.new(path, cref, {}, [])

      raw_decls.map do |raw_decl|
        AST.create_rbs_decl(raw_decl, lenv)
      end
    end

    def self.create_rbs_decl(raw_decl, lenv)
      case raw_decl
      when RBS::AST::Declarations::Class
        SigClassNode.new(raw_decl, lenv)
      when RBS::AST::Declarations::Module
        SigModuleNode.new(raw_decl, lenv)
      when RBS::AST::Declarations::Interface
        SigInterfaceNode.new(raw_decl, lenv)
      when RBS::AST::Declarations::Constant
        SigConstNode.new(raw_decl, lenv)
      when RBS::AST::Declarations::AliasDecl
      when RBS::AST::Declarations::TypeAlias
        SigTypeAliasNode.new(raw_decl, lenv)
        # TODO: check
      when RBS::AST::Declarations::Global
        SigGlobalVariableNode.new(raw_decl, lenv)
      else
        raise "unsupported: #{ raw_decl.class }"
      end
    end

    def self.create_rbs_member(raw_decl, lenv)
      case raw_decl
      when RBS::AST::Members::MethodDefinition
        SigDefNode.new(raw_decl, lenv)
      when RBS::AST::Members::Include
        SigIncludeNode.new(raw_decl, lenv)
      when RBS::AST::Members::Extend
      when RBS::AST::Members::Public
      when RBS::AST::Members::Private
      when RBS::AST::Members::Alias
        SigAliasNode.new(raw_decl, lenv)
      when RBS::AST::Members::AttrReader
        SigAttrReaderNode.new(raw_decl, lenv)
      when RBS::AST::Members::AttrWriter
        SigAttrWriterNode.new(raw_decl, lenv)
      when RBS::AST::Members::AttrAccessor
        SigAttrAccessorNode.new(raw_decl, lenv)
      when RBS::AST::Declarations::Base
        self.create_rbs_decl(raw_decl, lenv)
      else
        raise "unsupported: #{ raw_decl.class }"
      end
    end

    def self.create_rbs_func_type(raw_decl, raw_type_params, raw_block, lenv)
      SigFuncType.new(raw_decl, raw_type_params, raw_block, lenv)
    end

    def self.create_rbs_type(raw_decl, lenv)
      case raw_decl
      when RBS::Types::Bases::Nil
        SigTyBaseNilNode.new(raw_decl, lenv)
      when RBS::Types::Bases::Bool
        SigTyBaseBoolNode.new(raw_decl, lenv)
      when RBS::Types::Bases::Self
        SigTyBaseSelfNode.new(raw_decl, lenv)
      when RBS::Types::Bases::Void
        SigTyBaseVoidNode.new(raw_decl, lenv)
      when RBS::Types::Bases::Any
        SigTyBaseAnyNode.new(raw_decl, lenv)
      when RBS::Types::Bases::Top
        SigTyBaseTopNode.new(raw_decl, lenv)
      when RBS::Types::Bases::Bottom
        SigTyBaseBottomNode.new(raw_decl, lenv)
      when RBS::Types::Bases::Instance
        SigTyBaseInstanceNode.new(raw_decl, lenv)
      when RBS::Types::Bases::Class
        SigTyBaseClassNode.new(raw_decl, lenv)

      when RBS::Types::Alias
        SigTyAliasNode.new(raw_decl, lenv)
      when RBS::Types::Union
        SigTyUnionNode.new(raw_decl, lenv)
      when RBS::Types::Intersection
        SigTyIntersectionNode.new(raw_decl, lenv)
      when RBS::Types::ClassSingleton
        SigTySingletonNode.new(raw_decl, lenv)
      when RBS::Types::ClassInstance
        SigTyInstanceNode.new(raw_decl, lenv)
      when RBS::Types::Tuple
        SigTyTupleNode.new(raw_decl, lenv)
      when RBS::Types::Record
        SigTyRecordNode.new(raw_decl, lenv)
      when RBS::Types::Interface
        SigTyInterfaceNode.new(raw_decl, lenv)
      when RBS::Types::Proc
        SigTyProcNode.new(raw_decl, lenv)
      when RBS::Types::Variable
        SigTyVarNode.new(raw_decl, lenv)
      when RBS::Types::Optional
        SigTyOptionalNode.new(raw_decl, lenv)
      when RBS::Types::Literal
        SigTyLiteralNode.new(raw_decl, lenv)
      else
        raise "unknown RBS type: #{ raw_decl.class }"
      end
    end
  end
end
