module RBS
  module AST
    module Declarations
      class Base
      end

      class ModuleTypeParams
        attr_reader :params

        TypeParam = _ = Struct.new(:name, :variance, :skip_validation, keyword_init: true) do
          def to_json(*a)
            {
              name: name,
              variance: variance,
              skip_validation: skip_validation,
            }.to_json(*a)
          end
        end

        def initialize()
          @params = []
        end

        def add(param)
          params << param
          self
        end

        def ==(other)
          other.is_a?(ModuleTypeParams) && other.params == params
        end

        alias eql? ==

        def hash
          params.hash
        end

        def [](name)
          params.find {|p| p.name == name }
        end

        def to_json(*a)
          {
            params: params
          }.to_json(*a)
        end

        def each(&block)
          if block
            params.each(&block)
          else
            params.each
          end
        end

        def self.empty
          new
        end

        def variance(name)
          var = self[name] or raise
          var.variance
        end

        def skip_validation?(name)
          var = self[name] or raise
          var.skip_validation
        end

        def empty?
          params.empty?
        end

        def size
          params.size
        end

        def rename_to(names)
          ModuleTypeParams.new().tap do |params|
            names.each.with_index do |new_name, index|
              param = self.params[index]
              params.add(TypeParam.new(name: new_name, variance: param.variance, skip_validation: param.skip_validation))
            end
          end
        end
      end

      module NestedDeclarationHelper
        def each_member
          if block_given?
            members.each do |member|
              if member.is_a?(Members::Base)
                yield(_ = member)
              end
            end
          else
            enum_for :each_member
          end
        end

        def each_decl
          if block_given?
            members.each do |member|
              if member.is_a?(Base)
                yield(_ = member)
              end
            end
          else
            enum_for :each_decl
          end
        end
      end

      module MixinHelper
        def each_mixin(&block)
          if block
            @mixins ||= begin
                          _ = members.select do |member|
                            case member
                            when Members::Include, Members::Extend, Members::Prepend
                              true
                            else
                              false
                            end
                          end
                        end
            @mixins.each(&block)
          else
            enum_for :each_mixin
          end
        end
      end

      class Class < Base
        class Super
          attr_reader :name
          attr_reader :args
          attr_reader :location

          def initialize(name:, args:, location:)
            @name = name
            @args = args
            @location = location
          end

          def ==(other)
            other.is_a?(Super) && other.name == name && other.args == args
          end

          alias eql? ==

          def hash
            self.class.hash ^ name.hash ^ args.hash
          end

          def to_json(*a)
            {
              name: name,
              args: args,
              location: location
            }.to_json(*a)
          end
        end

        include NestedDeclarationHelper
        include MixinHelper

        attr_reader :name
        attr_reader :type_params
        attr_reader :members
        attr_reader :super_class
        attr_reader :annotations
        attr_reader :location
        attr_reader :comment

        def initialize(name:, type_params:, super_class:, members:, annotations:, location:, comment:)
          @name = name
          @type_params = type_params
          @super_class = super_class
          @members = members
          @annotations = annotations
          @location = location
          @comment = comment
        end

        def ==(other)
          other.is_a?(Class) &&
            other.name == name &&
            other.type_params == type_params &&
            other.super_class == super_class &&
            other.members == members
        end

        alias eql? ==

        def hash
          self.class.hash ^ name.hash ^ type_params.hash ^ super_class.hash ^ members.hash
        end

        def to_json(*a)
          {
            declaration: :class,
            name: name,
            type_params: type_params,
            members: members,
            super_class: super_class,
            annotations: annotations,
            location: location,
            comment: comment
          }.to_json(*a)
        end
      end

      class Module < Base
        class Self
          attr_reader :name
          attr_reader :args
          attr_reader :location

          def initialize(name:, args:, location:)
            @name = name
            @args = args
            @location = location
          end

          def ==(other)
            other.is_a?(Self) && other.name == name && other.args == args
          end

          alias eql? ==

          def hash
            self.class.hash ^ name.hash ^ args.hash ^ location.hash
          end

          def to_json(*a)
            {
              name: name,
              args: args,
              location: location
            }.to_json(*a)
          end

          def to_s
            if args.empty?
              name.to_s
            else
              "#{name}[#{args.join(", ")}]"
            end
          end
        end

        include NestedDeclarationHelper
        include MixinHelper

        attr_reader :name
        attr_reader :type_params
        attr_reader :members
        attr_reader :location
        attr_reader :annotations
        attr_reader :self_types
        attr_reader :comment

        def initialize(name:, type_params:, members:, self_types:, annotations:, location:, comment:)
          @name = name
          @type_params = type_params
          @self_types = self_types
          @members = members
          @annotations = annotations
          @location = location
          @comment = comment
        end

        def ==(other)
          other.is_a?(Module) &&
            other.name == name &&
            other.type_params == type_params &&
            other.self_types == self_types &&
            other.members == members
        end

        alias eql? ==

        def hash
          self.class.hash ^ name.hash ^ type_params.hash ^ self_types.hash ^ members.hash
        end

        def to_json(*a)
          {
            declaration: :module,
            name: name,
            type_params: type_params,
            members: members,
            self_types: self_types,
            annotations: annotations,
            location: location,
            comment: comment
          }.to_json(*a)
        end
      end

      class Interface < Base
        attr_reader :name
        attr_reader :type_params
        attr_reader :members
        attr_reader :annotations
        attr_reader :location
        attr_reader :comment

        include MixinHelper

        def initialize(name:, type_params:, members:, annotations:, location:, comment:)
          @name = name
          @type_params = type_params
          @members = members
          @annotations = annotations
          @location = location
          @comment = comment
        end

        def ==(other)
          other.is_a?(Interface) &&
            other.name == name &&
            other.type_params == type_params &&
            other.members == members
        end

        alias eql? ==

        def hash
          self.class.hash ^ type_params.hash ^ members.hash
        end

        def to_json(*a)
          {
            declaration: :interface,
            name: name,
            type_params: type_params,
            members: members,
            annotations: annotations,
            location: location,
            comment: comment
          }.to_json(*a)
        end
      end

      class Alias < Base
        attr_reader :name
        attr_reader :type
        attr_reader :annotations
        attr_reader :location
        attr_reader :comment

        def initialize(name:, type:, annotations:, location:, comment:)
          @name = name
          @type = type
          @annotations = annotations
          @location = location
          @comment = comment
        end

        def ==(other)
          other.is_a?(Alias) &&
            other.name == name &&
            other.type == type
        end

        alias eql? ==

        def hash
          self.class.hash ^ name.hash ^ type.hash
        end

        def to_json(*a)
          {
            declaration: :alias,
            name: name,
            type: type,
            annotations: annotations,
            location: location,
            comment: comment
          }.to_json(*a)
        end
      end

      class Constant < Base
        attr_reader :name
        attr_reader :type
        attr_reader :location
        attr_reader :comment

        def initialize(name:, type:, location:, comment:)
          @name = name
          @type = type
          @location = location
          @comment = comment
        end

        def ==(other)
          other.is_a?(Constant) &&
            other.name == name &&
            other.type == type
        end

        alias eql? ==

        def hash
          self.class.hash ^ name.hash ^ type.hash
        end

        def to_json(*a)
          {
            declaration: :constant,
            name: name,
            type: type,
            location: location,
            comment: comment
          }.to_json(*a)
        end
      end

      class Global < Base
        attr_reader :name
        attr_reader :type
        attr_reader :location
        attr_reader :comment

        def initialize(name:, type:, location:, comment:)
          @name = name
          @type = type
          @location = location
          @comment = comment
        end

        def ==(other)
          other.is_a?(Global) &&
            other.name == name &&
            other.type == type
        end

        alias eql? ==

        def hash
          self.class.hash ^ name.hash ^ type.hash
        end

        def to_json(*a)
          {
            declaration: :global,
            name: name,
            type: type,
            location: location,
            comment: comment
          }.to_json(*a)
        end
      end
    end
  end
end
