# frozen_string_literal: true

module Zapata
  module RZpec
    class Writer
      class << self
        attr_accessor :ivars

        def reset_ivars
          @ivars = []
        end
      end

      def initialize(file, _code, subject_analysis, whole_analysis, spec_analysis = nil)
        self.class.reset_ivars
        @subject_analysis = subject_analysis
        @whole_analysis = whole_analysis
        @spec_analysis = spec_analysis
        @writer = Core::Writer.new(file)
        @result = {}

        write_require

        klasses.each do |klass|
          write_class(klass)
        end

        self.class.reset_ivars
      end

      def write_require
        @writer.append_line("require '#{Core::Loader.helper_name}'")
      end

      def klasses
        @subject_analysis.select { |obj| obj.is_a?(Primitive::Klass) }
      end

      def klass_defs(klass)
        @subject_analysis.select do |method|
          [Primitive::Def, Primitive::Defs].include?(method.class) &&
            method.public? && method.klass.name == klass.name
        end
      end

      def initialize_def(klass)
        klass_defs(klass).detect { |method| method.name == :initialize }
      end

      def write_class(klass)
        @writer.append_line
        @writer.append_line("describe #{klass.name} do")

        write_instance_let(klass)

        klass_defs(klass).each do |primitive_def|
          write_method(primitive_def)
        end

        self.class.ivars.each do |ivar|
          predicted_value = Predictor::Value.new(ivar.value, ivar).choose
          literal_predicted_value = Printer.print(predicted_value.to_raw)
          write_let(ivar.value, literal_predicted_value)
        end

        @writer.append_line('end')
      end

      def write_instance_let(klass)
        if initialize_def = initialize_def(klass)
          write_let_from_initialize(initialize_def)
        else
          write_let(klass.name, "#{klass.name}.new")
        end
      end

      def write_let(name, block)
        @writer.append_line("let(:#{Printer.to_var_name(name)}) do")
        @writer.append_line(block)
        @writer.append_line('end')
      end

      def write_let_from_initialize(initialize_def)
        block = "#{initialize_def.klass.name}.new#{initialize_def.literal_predicted_args}"
        write_let(initialize_def.klass.name, block)
      end

      def write_method(primitive_def)
        return unless primitive_def.node.body
        return if primitive_def.name == :initialize

        @writer.append_line
        @writer.append_line("it '##{primitive_def.name}' do")

        receiver = if primitive_def.self?
          primitive_def.klass.name
        else
          Printer.to_var_name(primitive_def.klass.name)
        end

        @writer.append_line(
          "expect(#{receiver}.#{primitive_def.name}#{primitive_def.literal_predicted_args}).to eq(#{write_equal(primitive_def.name)})"
        )

        @writer.append_line('end')
      end

      def write_equal(method_name)
        if @spec_analysis
          Printer.print(Primitive::Raw.new(:literal, @spec_analysis.expected(method_name)))
        else
          Printer.print(Primitive::Raw.new(:str, 'Fill this in by hand'))
        end
      end
    end
  end
end
