class Steep::Interface::Params

def self.empty

def self.empty
  self.new(
    required: [],
    optional: [],
    rest: nil,
    required_keywords: {},
    optional_keywords: {},
    rest_keywords: nil
  )
end

def &(other)

def &(other)
  a = first_param
  b = other.first_param
  case
  when a.is_a?(RequiredPositional) && b.is_a?(RequiredPositional)
    AST::Types::Intersection.build(types: [a.type, b.type]).yield_self do |type|
      (self.drop_first & other.drop_first).with_first_param(RequiredPositional.new(type))
    end
  when a.is_a?(RequiredPositional) && b.is_a?(OptionalPositional)
    AST::Types::Intersection.build(types: [a.type, b.type]).yield_self do |type|
      (self.drop_first & other.drop_first).with_first_param(RequiredPositional.new(type))
    end
  when a.is_a?(RequiredPositional) && b.is_a?(RestPositional)
    AST::Types::Intersection.build(types: [a.type, b.type]).yield_self do |type|
      (self.drop_first & other).with_first_param(RequiredPositional.new(type))
    end
  when a.is_a?(RequiredPositional) && b.nil?
    (self.drop_first & other).with_first_param(RequiredPositional.new(AST::Types::Bot.new))
  when a.is_a?(OptionalPositional) && b.is_a?(RequiredPositional)
    AST::Types::Intersection.build(types: [a.type, b.type]).yield_self do |type|
      (self.drop_first & other.drop_first).with_first_param(RequiredPositional.new(type))
    end
  when a.is_a?(OptionalPositional) && b.is_a?(OptionalPositional)
    AST::Types::Intersection.build(types: [a.type, b.type]).yield_self do |type|
      (self.drop_first & other.drop_first).with_first_param(OptionalPositional.new(type))
    end
  when a.is_a?(OptionalPositional) && b.is_a?(RestPositional)
    AST::Types::Intersection.build(types: [a.type, b.type]).yield_self do |type|
      (self.drop_first & other).with_first_param(OptionalPositional.new(type))
    end
  when a.is_a?(OptionalPositional) && b.nil?
    self.drop_first & other
  when a.is_a?(RestPositional) && b.is_a?(RequiredPositional)
    AST::Types::Intersection.build(types: [a.type, b.type]).yield_self do |type|
      (self & other.drop_first).with_first_param(RequiredPositional.new(type))
    end
  when a.is_a?(RestPositional) && b.is_a?(OptionalPositional)
    AST::Types::Intersection.build(types: [a.type, b.type]).yield_self do |type|
      (self & other.drop_first).with_first_param(OptionalPositional.new(type))
    end
  when a.is_a?(RestPositional) && b.is_a?(RestPositional)
    AST::Types::Intersection.build(types: [a.type, b.type]).yield_self do |type|
      (self.drop_first & other.drop_first).with_first_param(RestPositional.new(type))
    end
  when a.is_a?(RestPositional) && b.nil?
    self.drop_first & other
  when a.nil? && b.is_a?(RequiredPositional)
    (self & other.drop_first).with_first_param(RequiredPositional.new(AST::Types::Bot.new))
  when a.nil? && b.is_a?(OptionalPositional)
    self & other.drop_first
  when a.nil? && b.is_a?(RestPositional)
    self & other.drop_first
  when a.nil? && b.nil?
    optional_keywords = {}
    (Set.new(self.optional_keywords.keys) & Set.new(other.optional_keywords.keys)).each do |keyword|
      self.optional_keywords[keyword] = AST::Types::Intersection.build(
        types: [
          self.optional_keywords[keyword],
          other.optional_keywords[keyword]
        ]
      )
    end
    required_keywords = {}
    self.optional_keywords.each do |keyword, t|
      unless optional_keywords.key?(keyword)
        case
        when other.required_keywords.key?(keyword)
          required_keywords[keyword] = AST::Types::Intersection.build(types: [t, other.required_keywords[keyword]])
        when other.rest_keywords
          required_keywords[keyword] = AST::Types::Intersection.build(types: [t, other.rest_keywords])
        else
          required_keywords[keyword] = t
        end
      end
    end
    other.optional_keywords.each do |keyword, t|
      unless optional_keywords.key?(keyword)
        case
        when self.required_keywords.key?(keyword)
          required_keywords[keyword] = AST::Types::Intersection.build(types: [t, self.required_keywords[keyword]])
        when self.rest_keywords
          required_keywords[keyword] = AST::Types::Intersection.build(types: [t, self.rest_keywords])
        else
          required_keywords[keyword] = t
        end
      end
    end
    self.required_keywords.each do |keyword, t|
      unless required_keywords.key?(keyword)
        case
        when other.required_keywords.key?(keyword)
          required_keywords[keyword] = AST::Types::Intersection.build(types: [t, other.required_keywords[keyword]])
        when other.rest_keywords
          required_keywords[keyword] = AST::Types::Intersection.build(types: [t, other.rest_keywords])
        else
          required_keywords[keyword] = t
        end
      end
    end
    other.required_keywords.each do |keyword, t|
      unless required_keywords.key?(keyword)
        case
        when self.required_keywords.key?(keyword)
          required_keywords[keyword] = AST::Types::Intersection.build(types: [t, self.required_keywords[keyword]])
        when self.rest_keywords
          required_keywords[keyword] = AST::Types::Intersection.build(types: [t, self.rest_keywords])
        else
          required_keywords[keyword] = t
        end
      end
    end
    rest = case
           when self.rest_keywords && other.rest_keywords
             AST::Types::Intersection.build(types: [self.rest_keywords, other.rest_keywords])
           else
             self.rest_keywords || other.rest_keywords
           end
    Params.new(
      required: [],
      optional: [],
      rest: nil,
      required_keywords: required_keywords,
      optional_keywords: optional_keywords,
      rest_keywords: rest)
  end
end

def ==(other)

def ==(other)
  other.is_a?(self.class) &&
    other.required == required &&
    other.optional == optional &&
    other.rest == rest &&
    other.required_keywords == required_keywords &&
    other.optional_keywords == optional_keywords &&
    other.rest_keywords == rest_keywords
end

def closed?

def closed?
  required.all?(&:closed?) && optional.all?(&:closed?) && (!rest || rest.closed?) && required_keywords.values.all?(&:closed?) && optional_keywords.values.all?(&:closed?) && (!rest_keywords || rest_keywords.closed?)
end

def drop_first

def drop_first
  case
  when required.any? || optional.any? || rest
    self.class.new(
      required: required.any? ? required.drop(1) : [],
      optional: required.empty? && optional.any? ? optional.drop(1) : optional,
      rest: required.empty? && optional.empty? ? nil : rest,
      required_keywords: required_keywords,
      optional_keywords: optional_keywords,
      rest_keywords: rest_keywords
    )
  when has_keywords?
    without_keywords
  else
    raise "Cannot drop from empty params"
  end
end

def each_extra_argument(args)

def each_extra_argument(args)
  return if rest
  if has_keywords?
    args = args.take(args.count - 1) if args.count > 0
  end
  args.size.times do |index|
    if index >= required.count + optional.count
      yield index
    end
  end
end

def each_extra_keyword(args)

def each_extra_keyword(args)
  return unless has_keywords?
  return if rest_keywords
  keywords, rest = extract_keywords(args)
  return unless rest.empty?
  all_keywords = flat_keywords
  keywords.each do |keyword, _|
    yield keyword unless all_keywords.key?(keyword)
  end
end

def each_missing_argument(args)

def each_missing_argument(args)
  required.size.times do |index|
    if index >= args.size
      yield index
    end
  end
end

def each_missing_keyword(args)

def each_missing_keyword(args)
  return unless has_keywords?
  keywords, rest = extract_keywords(args)
  return unless rest.empty?
  required_keywords.each do |keyword, _|
    yield keyword unless keywords.key?(keyword)
  end
end

def each_type()

def each_type()
  if block_given?
    flat_unnamed_params.each do |(_, type)|
      yield type
    end
    flat_keywords.each do |_, type|
      yield type
    end
    rest and yield rest
    rest_keywords and yield rest_keywords
  else
    enum_for :each_type
  end
end

def empty?

def empty?
  !has_positional? && !has_keywords?
end

def extract_keywords(args)

def extract_keywords(args)
  last_arg = args.last
  keywords = {}
  rest = []
  if last_arg&.type == :hash
    last_arg.children.each do |element|
      case element.type
      when :pair
        if element.children[0].type == :sym
          name = element.children[0].children[0]
          keywords[name] = element.children[1]
        end
      when :kwsplat
        rest << element.children[0]
      end
    end
  end
  [keywords, rest]
end

def first_param

def first_param
  case
  when !required.empty?
    RequiredPositional.new(required[0])
  when !optional.empty?
    OptionalPositional.new(optional[0])
  when rest
    RestPositional.new(rest)
  else
    nil
  end
end

def flat_keywords

def flat_keywords
  required_keywords.merge optional_keywords
end

def flat_unnamed_params

def flat_unnamed_params
  required.map {|p| [:required, p] } + optional.map {|p| [:optional, p] }
end

def free_variables

def free_variables
  Set.new.tap do |fvs|
    each_type do |type|
      fvs.merge type.free_variables
    end
  end
end

def has_keywords?

def has_keywords?
  !required_keywords.empty? || !optional_keywords.empty? || rest_keywords
end

def has_positional?

def has_positional?
  first_param
end

def initialize(required:, optional:, rest:, required_keywords:, optional_keywords:, rest_keywords:)

def initialize(required:, optional:, rest:, required_keywords:, optional_keywords:, rest_keywords:)
  @required = required
  @optional = optional
  @rest = rest
  @required_keywords = required_keywords
  @optional_keywords = optional_keywords
  @rest_keywords = rest_keywords
end

def map_type(&block)

def map_type(&block)
  self.class.new(
    required: required.map(&block),
    optional: optional.map(&block),
    rest: rest && yield(rest),
    required_keywords: required_keywords.transform_values(&block),
    optional_keywords: optional_keywords.transform_values(&block),
    rest_keywords: rest_keywords && yield(rest_keywords)
  )
end

def size

def size
  required.size + optional.size + (rest ? 1 : 0) + required_keywords.size + optional_keywords.size + (rest_keywords ? 1 : 0)
end

def subst(s)

def subst(s)
  self.class.new(
    required: required.map {|t| t.subst(s) },
    optional: optional.map {|t| t.subst(s) },
    rest: rest&.subst(s),
    required_keywords: required_keywords.transform_values {|t| t.subst(s) },
    optional_keywords: optional_keywords.transform_values {|t| t.subst(s) },
    rest_keywords: rest_keywords&.subst(s)
  )
end

def to_s

def to_s
  required = self.required.map {|ty| ty.to_s }
  optional = self.optional.map {|ty| "?#{ty}" }
  rest = self.rest ? ["*#{self.rest}"] : []
  required_keywords = self.required_keywords.map {|name, type| "#{name}: #{type}" }
  optional_keywords = self.optional_keywords.map {|name, type| "?#{name}: #{type}"}
  rest_keywords = self.rest_keywords ? ["**#{self.rest_keywords}"] : []
  "(#{(required + optional + rest + required_keywords + optional_keywords + rest_keywords).join(", ")})"
end

def update(required: NONE, optional: NONE, rest: NONE, required_keywords: NONE, optional_keywords: NONE, rest_keywords: NONE)

def update(required: NONE, optional: NONE, rest: NONE, required_keywords: NONE, optional_keywords: NONE, rest_keywords: NONE)
  self.class.new(
    required: required.equal?(NONE) ? self.required : required,
    optional: optional.equal?(NONE) ? self.optional : optional,
    rest: rest.equal?(NONE) ? self.rest : rest,
    required_keywords: required_keywords.equal?(NONE) ? self.required_keywords : required_keywords,
    optional_keywords: optional_keywords.equal?(NONE) ? self.optional_keywords : optional_keywords,
    rest_keywords: rest_keywords.equal?(NONE) ? self.rest_keywords : rest_keywords
  )
end

def with_first_param(param)

def with_first_param(param)
  case param
  when RequiredPositional
    update(required: [param.type] + required)
  when OptionalPositional
    update(optional: [param.type] + required)
  when RestPositional
    update(rest: param.type)
  else
    self
  end
end

def without_keywords

def without_keywords
  self.class.new(
    required: required,
    optional: optional,
    rest: rest,
    required_keywords: {},
    optional_keywords: {},
    rest_keywords: nil
  )
end

def |(other)

def |(other)
  a = first_param
  b = other.first_param
  case
  when a.is_a?(RequiredPositional) && b.is_a?(RequiredPositional)
    AST::Types::Union.build(types: [a.type, b.type]).yield_self do |type|
      (self.drop_first | other.drop_first).with_first_param(RequiredPositional.new(type))
    end
  when a.is_a?(RequiredPositional) && b.is_a?(OptionalPositional)
    AST::Types::Union.build(types: [a.type, b.type]).yield_self do |type|
      (self.drop_first | other.drop_first).with_first_param(OptionalPositional.new(type))
    end
  when a.is_a?(RequiredPositional) && b.is_a?(RestPositional)
    AST::Types::Union.build(types: [a.type, b.type]).yield_self do |type|
      (self.drop_first | other).with_first_param(OptionalPositional.new(type))
    end
  when a.is_a?(RequiredPositional) && b.nil?
    (self.drop_first | other).with_first_param(OptionalPositional.new(a.type))
  when a.is_a?(OptionalPositional) && b.is_a?(RequiredPositional)
    AST::Types::Union.build(types: [a.type, b.type]).yield_self do |type|
      (self.drop_first | other.drop_first).with_first_param(OptionalPositional.new(type))
    end
  when a.is_a?(OptionalPositional) && b.is_a?(OptionalPositional)
    AST::Types::Union.build(types: [a.type, b.type]).yield_self do |type|
      (self.drop_first | other.drop_first).with_first_param(OptionalPositional.new(type))
    end
  when a.is_a?(OptionalPositional) && b.is_a?(RestPositional)
    AST::Types::Union.build(types: [a.type, b.type]).yield_self do |type|
      (self.drop_first | other).with_first_param(OptionalPositional.new(type))
    end
  when a.is_a?(OptionalPositional) && b.nil?
    (self.drop_first | other).with_first_param(OptionalPositional.new(a.type))
  when a.is_a?(RestPositional) && b.is_a?(RequiredPositional)
    AST::Types::Union.build(types: [a.type, b.type]).yield_self do |type|
      (self | other.drop_first).with_first_param(OptionalPositional.new(type))
    end
  when a.is_a?(RestPositional) && b.is_a?(OptionalPositional)
    AST::Types::Union.build(types: [a.type, b.type]).yield_self do |type|
      (self | other.drop_first).with_first_param(OptionalPositional.new(type))
    end
  when a.is_a?(RestPositional) && b.is_a?(RestPositional)
    AST::Types::Union.build(types: [a.type, b.type]).yield_self do |type|
      (self.drop_first | other.drop_first).with_first_param(RestPositional.new(type))
    end
  when a.is_a?(RestPositional) && b.nil?
    (self.drop_first | other).with_first_param(RestPositional.new(a.type))
  when a.nil? && b.is_a?(RequiredPositional)
    (self | other.drop_first).with_first_param(OptionalPositional.new(b.type))
  when a.nil? && b.is_a?(OptionalPositional)
    (self | other.drop_first).with_first_param(OptionalPositional.new(b.type))
  when a.nil? && b.is_a?(RestPositional)
    (self | other.drop_first).with_first_param(RestPositional.new(b.type))
  when a.nil? && b.nil?
    required_keywords = {}
    (Set.new(self.required_keywords.keys) & Set.new(other.required_keywords.keys)).each do |keyword|
      required_keywords[keyword] = AST::Types::Union.build(
        types: [
          self.required_keywords[keyword],
          other.required_keywords[keyword]
        ]
      )
    end
    optional_keywords = {}
    self.required_keywords.each do |keyword, t|
      unless required_keywords.key?(keyword)
        case
        when other.optional_keywords.key?(keyword)
          optional_keywords[keyword] = AST::Types::Union.build(types: [t, other.optional_keywords[keyword]])
        when other.rest_keywords
          optional_keywords[keyword] = AST::Types::Union.build(types: [t, other.rest_keywords])
        else
          optional_keywords[keyword] = t
        end
      end
    end
    other.required_keywords.each do |keyword, t|
      unless required_keywords.key?(keyword)
        case
        when self.optional_keywords.key?(keyword)
          optional_keywords[keyword] = AST::Types::Union.build(types: [t, self.optional_keywords[keyword]])
        when self.rest_keywords
          optional_keywords[keyword] = AST::Types::Union.build(types: [t, self.rest_keywords])
        else
          optional_keywords[keyword] = t
        end
      end
    end
    self.optional_keywords.each do |keyword, t|
      unless optional_keywords.key?(keyword)
        case
        when other.optional_keywords.key?(keyword)
          optional_keywords[keyword] = AST::Types::Union.build(types: [t, other.optional_keywords[keyword]])
        when other.rest_keywords
          optional_keywords[keyword] = AST::Types::Union.build(types: [t, other.rest_keywords])
        else
          optional_keywords[keyword] = t
        end
      end
    end
    other.optional_keywords.each do |keyword, t|
      unless optional_keywords.key?(keyword)
        case
        when self.optional_keywords.key?(keyword)
          optional_keywords[keyword] = AST::Types::Union.build(types: [t, self.optional_keywords[keyword]])
        when self.rest_keywords
          optional_keywords[keyword] = AST::Types::Union.build(types: [t, self.rest_keywords])
        else
          optional_keywords[keyword] = t
        end
      end
    end
    rest = case
           when self.rest_keywords && other.rest_keywords
             AST::Types::Union.build(types: [self.rest_keywords, other.rest_keywords])
           else
             self.rest_keywords || other.rest_keywords
           end
    Params.new(
            required: [],
            optional: [],
            rest: nil,
            required_keywords: required_keywords,
            optional_keywords: optional_keywords,
            rest_keywords: rest)
  end
end