# typed: strict
module Tapioca::Compilers
  class AttrJsonDsl < Tapioca::Dsl::Compiler
    extend T::Sig

    ConstantType = type_member { { fixed: T.class_of(::AttrJson::Record) } }

    sig { override.returns(T::Enumerable[Module]) }
    def self.gather_constants
      all_classes.select { |c| c < ::AttrJson::Record }
    end

    sig { override.void }
    def decorate
      root.create_path(constant) do |klass|
        klass.create_method(
          "attr_json_config",
          parameters: [
            create_kw_opt_param(
              "default_container_attribute",
              type: "T.nilable(Symbol)",
              default: "nil",
            ),
            create_kw_opt_param(
              "bad_cast",
              type: "T.nilable(Symbol)",
              default: "nil",
            ),
            create_kw_opt_param(
              "unknown_key",
              type: "T.nilable(Symbol)",
              default: "nil",
            ),
          ],
          return_type: "void",
          class_method: true,
        )
        klass.create_method(
          "attr_json",
          parameters: [
            create_param("name", type: "Symbol"),
            create_param(
              "type",
              type: "T.any(Symbol, ActiveModel::Type::Value)",
            ),
            create_opt_param(
              "options",
              type: "T.nilable(T::Hash[Symbol, T.untyped])",
              default: "nil",
            ),
          ],
          return_type: "void",
          class_method: true,
        )
        klass.create_method(
          "attr_json_registry",
          return_type: "T::Array[Symbol]",
          class_method: true,
        )

        registry = T.unsafe(constant).attr_json_registry
        T
          .cast(registry.attribute_names, T::Array[Symbol])
          .each do |name|
            attribute = T.cast(registry[name], AttrJson::AttributeDefinition)
            if (type = attribute.type) && type.is_a?(AttrJson::Type::Model) &&
                 (model = type.model) && model < AttrJson::Model
              klass.create_method(
                name.to_s,
                return_type: "T.nilable(#{model.name})",
              )
            end
          end
      end
    end
  end
end
