# typed: strict
# frozen_string_literal: true

begin
  require "kredis"
rescue LoadError
  return
end

module Tapioca
  module Dsl
    module Compilers
      # `Tapioca::Dsl::Compilers::Kredis` decorates RBI files for all
      # classes that include [`Kredis::Attributes`](https://github.com/rails/kredis/blob/main/lib/kredis/attributes.rb).
      #
      # For example, with the following class:
      #
      # ~~~rb
      # class Person < ApplicationRecord
      #   kredis_list :names
      #   kredis_flag :awesome
      #   kredis_counter :steps, expires_in: 1.hour
      #   kredis_enum :morning, values: %w[ bright blue black ], default: "bright"
      # end
      # ~~~
      #
      # this compiler will produce an RBI file with the following content:
      # ~~~rbi
      # # typed: true
      #
      # class Person
      #   module GeneratedKredisAttributeMethods
      #     sig { returns(Kredis::Types::Flag) }
      #     def awesome; end
      #
      #     sig { returns(T::Boolean) }
      #     def awesome?; end
      #
      #     sig { returns(PrivateEnumMorning) }
      #     def morning; end
      #
      #     sig { returns(Kredis::Types::List) }
      #     def names; end
      #
      #     sig { returns(Kredis::Types::Counter) }
      #     def steps; end
      #
      #     class PrivateEnumMorning < Kredis::Types::Enum
      #       sig { void }
      #       def black!; end
      #
      #       sig { returns(T::Boolean) }
      #       def black?; end
      #
      #       sig { void }
      #       def blue!; end
      #
      #       sig { returns(T::Boolean) }
      #       def blue?; end
      #
      #       sig { void }
      #       def bright!; end
      #
      #       sig { returns(T::Boolean) }
      #       def bright?; end
      #     end
      #   end
      # end
      # ~~~
      class Kredis < Compiler
        extend T::Sig

        ConstantType = type_member do
          { fixed: T.all(T::Class[::Kredis::Attributes], ::Kredis::Attributes::ClassMethods, Extensions::Kredis) }
        end

        sig { override.void }
        def decorate
          return if constant.__tapioca_kredis_types.nil?

          module_name = "GeneratedKredisAttributeMethods"

          root.create_path(constant) do |model|
            model.create_module(module_name) do |mod|
              constant.__tapioca_kredis_types.each do |method, data|
                generate_methods(mod, method, data)
              end
            end
            model.create_include(module_name)
          end
        end

        class << self
          extend T::Sig

          sig { override.returns(T::Enumerable[Module]) }
          def gather_constants
            all_classes
              .grep(::Kredis::Attributes::ClassMethods)
              .reject { |klass| klass.to_s == "ActiveRecord::Base" || klass.try(:abstract_class?) }
          end
        end

        private

        sig { params(mod: RBI::Scope, method: String, data: T::Hash[Symbol, T.untyped]).void }
        def generate_methods(mod, method, data)
          return_type = data.fetch(:type)
          case return_type
          when "Kredis::Types::Enum"
            klass_name = "PrivateEnum#{method.split("_").map(&:capitalize).join}"
            create_enum_class(mod, klass_name, data.fetch(:values))
            return_type = klass_name
          when "Kredis::Types::Flag"
            mod.create_method("#{method}?", return_type: "T::Boolean")
          end

          mod.create_method(method, return_type: return_type)
        end

        sig { params(mod: RBI::Scope, klass_name: String, values: T::Array[T.untyped]).void }
        def create_enum_class(mod, klass_name, values)
          klass = mod.create_class(klass_name, superclass_name: "Kredis::Types::Enum")
          values.each do |value|
            klass.create_method("#{value}!", return_type: "void")
            klass.create_method("#{value}?", return_type: "T::Boolean")
          end
        end
      end
    end
  end
end