# frozen_string_literal: true

class Code
  class Object
    class Function < ::Code::Object
      attr_reader :parameters, :body

      def initialize(parameters:, body:)
        @parameters = parameters
        @body = body
      end

      def self.name
        "Function"
      end

      def call(**args)
        operator = args.fetch(:operator, nil)
        arguments = args.fetch(:arguments, [])
        globals = multi_fetch(args, *::Code::GLOBALS)

        case operator.to_s
        when "", "call"
          sig(args) { signature_for_call }
          code_call(*arguments, **globals)
        else
          super
        end
      end

      def code_call(*arguments, **globals)
        context = Context.new({}, parent: globals[:context])

        parameters.each.with_index do |parameter, index|
          if parameter.regular?
            if parameter.regular_splat?
              context.code_set(
                parameter.name,
                List.new(arguments.select(&:regular?).map(&:value))
              )
            elsif parameter.keyword_splat?
              context.code_set(
                parameter.name,
                Dictionary.new(
                  arguments.select(&:keyword?).map(&:name_value).to_h
                )
              )
            else
              argument = arguments[index]&.value
              argument = parameter.evaluate(**globals) if argument.nil?
              context.code_set(parameter.name, argument)
            end
          elsif parameter.keyword?
            argument =
              arguments
                .detect { |argument| argument.name == parameter.name }
                &.value
            argument = parameter.evaluate(**globals) if argument.nil?
            context.code_set(parameter.name, argument)
          else
            raise NotImplementedError
          end
        end

        body.evaluate(**globals, context:)
      end

      def inspect
        "function"
      end

      def signature_for_call
        parameters.inject([]) do |signature, parameter|
          if parameter.keyword?
            if signature.last.is_a?(::Hash)
              signature.last[parameter.name] = Object
              signature
            else
              signature + [{ parameter.name => Object }]
            end
          else
            signature + [Object]
          end
        end + [Object.repeat]
      end

      def to_s
        ""
      end

      def as_json(...)
        raw.as_json(...)
      end
    end
  end
end