class Generators::Avo::ResourceGenerator

def associations

def associations
  @associations ||= reflections.reject do |key|
    attachments.key?(key) || tags.key?(key) || rich_texts.key?(key)
  end
end

def attachments

def attachments
  @attachments ||= reflections.select do |_, reflection|
    reflection.options[:class_name] == "ActiveStorage::Attachment"
  end
end

def can_connect_to_the_database?

def can_connect_to_the_database?
  result = false
  begin
    ActiveRecord::Migration.check_all_pending!
    result = true
    # If all migrations were completed, try to generate some resource files
  rescue NoMethodError
    # Ignore #<NoMethodError: undefined method `check_all_pending!' for an instance of ActiveRecord::Migration>]
    result = true
  rescue ActiveRecord::ConnectionNotEstablished
    puts error_message("Connection not established.\nRun 'rails db:setup' to resolve.")
  rescue ActiveRecord::PendingMigrationError
    puts error_message("Migrations are pending.\nRun 'rails db:migrate' to resolve.")
  rescue => e
    puts "Something went wrong while trying to generate an Avo resource: #{e}"
  end
  result
end

def class_from_args

def class_from_args
  @class_from_args ||= options["model-class"]&.camelize || (class_name if class_name.include?("::"))
end

def controller_class

def controller_class
  "Avo::#{class_name.remove(":").pluralize}Controller"
end

def controller_name

def controller_name
  "#{model_resource_name.pluralize}_controller"
end

def create

def create
  return if override_controller?
  template "resource/resource.tt", "app/avo/resources/#{resource_name}.rb"
  invoke "avo:controller", [resource_name], options
end

def current_models

def current_models
  ActiveRecord::Base.connection.tables.map do |model|
    model.capitalize.singularize.camelize
  end
rescue ActiveRecord::NoDatabaseError
  puts "Database not found, please create your database and regenerate the resource."
  []
rescue ActiveRecord::ConnectionNotEstablished
  puts "Database connection error, please create your database and regenerate the resource."
  []
end

def db_columns_to_ignore

def db_columns_to_ignore
  %w[id encrypted_password reset_password_token reset_password_sent_at remember_created_at created_at updated_at password_digest]
end

def error_message(extra)

def error_message(extra)
  "Avo will not attempt to create resources for you.\n#{extra}\nThen run 'rails generate avo:all_resources' to generate all your resources."
end

def field(name, type)

def field(name, type)
  ::Avo::Mappings::NAMES_MAPPING[name.to_sym] || ::Avo::Mappings::FIELDS_MAPPING[type&.to_sym] || {field: "text"}
end

def field_from_through_association(association)

def field_from_through_association(association)
  if association.through_reflection.is_a?(ActiveRecord::Reflection::HasManyReflection) || association.through_reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
    {
      field: "has_many",
      options: {
        through: ":#{association.options[:through]}"
      }
    }
  else
    # If the through_reflection is not a HasManyReflection, add it to the fields hash using the class of the through_reflection
    # ex (team.rb): has_one :admin, through: :admin_membership, source: :user
    # we use the class of the through_reflection (HasOneReflection -> has_one :admin) to generate the field
    associations_mapping[association.through_reflection.class]
  end
end

def field_string(name, type, options)

def field_string(name, type, options)
  "field :#{name}, as: :#{type}#{options}"
end

def fields

def fields
  @fields ||= {}
end

def fields_from_model_associations

def fields_from_model_associations
  associations.each do |name, association|
    fields[name] = if association.is_a? ActiveRecord::Reflection::ThroughReflection
      field_from_through_association(association)
    else
      ::Avo::Mappings::ASSOCIATIONS_MAPPING[association.class]
    end
  end
end

def fields_from_model_attachements

def fields_from_model_attachements
  attachments.each do |name, attachment|
    fields[remove_last_word_from name] = ::Avo::Mappings::ATTACHMENTS_MAPPING[attachment.class]
  end
end

def fields_from_model_db_columns

def fields_from_model_db_columns
  model_db_columns.each do |name, data|
    fields[name] = field(name, data.type)
  end
end

def fields_from_model_enums

def fields_from_model_enums
  model.defined_enums.each_key do |enum|
    fields[enum] = {
      field: "select",
      options: {
        enum: "::#{model_class.classify}.#{enum.pluralize}"
      }
    }
  end
end

def fields_from_model_rich_texts

def fields_from_model_rich_texts
  rich_texts.each do |name, _|
    fields[name.delete_prefix("rich_text_")] = {field: "trix"}
  end
end

def fields_from_model_tags

def fields_from_model_tags
  tags.each do |name, _|
    fields[(remove_last_word_from name).pluralize] = {field: "tags"}
  end
end

def generate_fields

def generate_fields
  return generate_fields_from_args if invoked_by_model_generator?
  return unless can_connect_to_the_database?
  if model.blank?
    puts "Can't generate fields from model. '#{model_class}.rb' not found!"
    return
  end
  fields_from_model_db_columns
  fields_from_model_enums
  fields_from_model_attachements
  fields_from_model_associations
  fields_from_model_rich_texts
  fields_from_model_tags
  generated_fields_template
end

def generate_fields_from_args

def generate_fields_from_args
  @args.each do |arg|
    name, type = arg.split(":")
    type = "string" if type.blank?
    fields[name] = field(name, type.to_sym)
  end
  generated_fields_template
end

def generated_fields_template

def generated_fields_template
  return if fields.blank?
  fields_string = ""
  fields.each do |field_name, field_options|
    # if field_options are not available (likely a missing resource for an association), skip the field
    fields_string += "\n    # Could not generate a field for #{field_name}" and next unless field_options
    options = ""
    field_options[:options].each { |k, v| options += ", #{k}: #{v}" } if field_options[:options].present?
    fields_string += "\n    #{field_string field_name, field_options[:field], options}"
  end
  fields_string
end

def invoked_by_model_generator?

def invoked_by_model_generator?
  @options.dig("from_model_generator")
end

def model

def model
  @model ||= model_class.classify.safe_constantize
end

def model_class

def model_class
  @model_class ||= class_from_args || singular_name
end

def model_class_from_args

def model_class_from_args
  if class_from_args.present? || class_name.include?("::")
    "\n  self.model_class = ::#{class_from_args || class_name}"
  end
end

def model_db_columns

def model_db_columns
  @model_db_columns ||= model.columns_hash.except(*db_columns_to_ignore)
rescue ActiveRecord::NoDatabaseError
  puts "Database not found, please create your database and regenerate the resource."
  []
rescue ActiveRecord::ConnectionNotEstablished
  puts "Database connection error, please create your database and regenerate the resource."
  []
end

def parent_resource

def parent_resource
  if options["array"]
    "Avo::Resources::ArrayResource"
  else
    "Avo::BaseResource"
  end
end

def reflections

def reflections
  @reflections ||= model.reflections.reject do |name, _|
    reflections_sufixes_to_ignore.include?(name.split("_").pop) || reflections_to_ignore.include?(name)
  end
end

def reflections_sufixes_to_ignore

def reflections_sufixes_to_ignore
  %w[blob blobs tags]
end

def reflections_to_ignore

def reflections_to_ignore
  %w[taggings]
end

def remove_last_word_from(snake_case_string)

['hello', 'world'].join('_') => "hello_world"
['hello', 'world', 'hehe'].pop => ['hello', 'world']
"hello_world_hehe".split('_') => ['hello', 'world', 'hehe']
def remove_last_word_from(snake_case_string)
  snake_case_string = snake_case_string.split("_")
  snake_case_string.pop
  snake_case_string.join("_")
end

def resource_class

def resource_class
  class_name.remove(":").to_s
end

def resource_name

def resource_name
  model_resource_name.to_s
end

def rich_texts

def rich_texts
  @rich_texts ||= reflections.select do |_, reflection|
    reflection.options[:class_name] == "ActionText::RichText"
  end
end

def tags

def tags
  @tags ||= reflections.select { |_, reflection| reflection.options[:as] == :taggable }
end