# typed: strict
# frozen_string_literal: true

begin
  require "active_record"
rescue LoadError
  return
end

require "tapioca/dsl/helpers/active_record_constants_helper"

module Tapioca
  module Dsl
    module Compilers
      # `Tapioca::Dsl::Compilers::ActiveRecordStore` decorates RBI files for all
      # classes that use [`ActiveRecord::Store`](https://api.rubyonrails.org/classes/ActiveRecord/Store.html).
      #
      # For example, with the following class:
      #
      # ~~~rb
      # class User < ActiveRecord::Base
      #   store :settings, accessors: :theme
      #   store_accessor :settings, :power_source, prefix: :prefs
      # end
      # ~~~
      #
      # this compiler will produce an RBI file with the following content:
      # ~~~rbi
      # # typed: true
      #
      # class User
      #   include GeneratedStoredAttributesMethods
      #
      #   module GeneratedStoredAttributesMethods
      #     sig { returns(T.untyped) }
      #     def prefs_power_source; end
      #
      #     sig { params(value: T.untyped).returns(T.untyped) }
      #     def prefs_power_source=(value); end
      #
      #     sig { returns(T.untyped) }
      #     def prefs_power_source_before_last_save; end
      #
      #     sig { returns(T.untyped) }
      #     def prefs_power_source_change; end
      #
      #     sig { returns(T::Boolean) }
      #     def prefs_power_source_changed?; end
      #
      #     sig { returns(T.untyped) }
      #     def prefs_power_source_was; end
      #
      #     sig { returns(T.untyped) }
      #     def saved_change_to_prefs_power_source; end
      #
      #     sig { returns(T::Boolean) }
      #     def saved_change_to_prefs_power_source?; end
      #
      #     sig { returns(T.untyped) }
      #     def saved_change_to_theme; end
      #
      #     sig { returns(T::Boolean) }
      #     def saved_change_to_theme?; end
      #
      #     sig { returns(T.untyped) }
      #     def theme; end
      #
      #     sig { params(value: T.untyped).returns(T.untyped) }
      #     def theme=(value); end
      #
      #     sig { returns(T.untyped) }
      #     def theme_before_last_save; end
      #
      #     sig { returns(T.untyped) }
      #     def theme_change; end
      #
      #     sig { returns(T::Boolean) }
      #     def theme_changed?; end
      #
      #     sig { returns(T.untyped) }
      #     def theme_was; end
      #   end
      # end
      # ~~~
      class ActiveRecordStore < Compiler
        extend T::Sig
        include Helpers::ActiveRecordConstantsHelper

        ConstantType = type_member { { fixed: T.all(T.class_of(ActiveRecord::Base), Extensions::ActiveRecord) } }

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

          root.create_path(constant) do |klass|
            klass.create_module(StoredAttributesModuleName) do |mod|
              constant.__tapioca_stored_attributes.each do |store_attribute, keys, prefix, suffix|
                accessor_prefix =
                  case prefix
                  when String, Symbol
                    "#{prefix}_"
                  when TrueClass
                    "#{store_attribute}_"
                  else
                    ""
                  end
                accessor_suffix =
                  case suffix
                  when String, Symbol
                    "_#{suffix}"
                  when TrueClass
                    "_#{store_attribute}"
                  else
                    ""
                  end

                keys.flatten.map { |key| "#{accessor_prefix}#{key}#{accessor_suffix}" }.each do |accessor_key|
                  mod.create_method(
                    "#{accessor_key}=",
                    parameters: [create_param("value", type: "T.untyped")],
                    return_type: "T.untyped",
                  )
                  mod.create_method(accessor_key, return_type: "T.untyped")
                  mod.create_method("#{accessor_key}_changed?", return_type: "T::Boolean")
                  mod.create_method("#{accessor_key}_change", return_type: "T.untyped")
                  mod.create_method("#{accessor_key}_was", return_type: "T.untyped")
                  mod.create_method("saved_change_to_#{accessor_key}?", return_type: "T::Boolean")
                  mod.create_method("saved_change_to_#{accessor_key}", return_type: "T.untyped")
                  mod.create_method("#{accessor_key}_before_last_save", return_type: "T.untyped")
                end
              end
            end

            klass.create_include(StoredAttributesModuleName)
          end
        end

        class << self
          extend T::Sig

          sig { override.returns(T::Enumerable[Module]) }
          def gather_constants
            descendants_of(::ActiveRecord::Base).reject(&:abstract_class?)
          end
        end
      end
    end
  end
end