# frozen_string_literal: true

module Decidim
  # Components inside a component can expose different Resources, these resources
  # will be used to be linked between each other and other possible components.
  #
  # This class sets a scheme to expose these resources. It should not be
  # used directly, you should use `register_resource` inside a component.
  #
  # Example:
  #   component.register_resource(:my_model) do |resource|
  #     resource.model_class = Decidim::MyEngine::MyModel
  #     resource.template    = "decidim/myengine/myengine/linked_models"
  #   end
  #
  class ResourceManifest
    include ActiveModel::Model
    include Decidim::AttributeObject::Model

    # The name of the resource we are exposing.
    attribute :name, String

    # The parent component manifest
    attribute :component_manifest, Decidim::ComponentManifest

    # The ActiveRecord class name of the model we are exposing
    attribute :model_class_name, String

    # The scope name of the content blocks associated with the resource
    attribute :content_blocks_scope_name, String

    # The name of the named Rails route to create the url to the resource.
    # When not explicitly set, it will use the model name.
    attribute :route_name, String

    # The template to use to render the collection of the resource.
    attribute :template, String

    # The main card to render an instance of the resource.
    attribute :card, String

    # The reported content to render an instance of the resource.
    attribute :reported_content_cell, String

    # Set this to `true` if you want this resource to be searchable. It requires
    # the model to include the `Decidim::Searchable` concern.
    attribute :searchable, Boolean, default: false

    # Actions are used to validate permissions of a resource against particular
    # authorizations or potentially other authorization rules.
    #
    # An example would be `vote` on a specific proposal, or `join` on a meeting.
    #
    # A resource can expose as many actions as it wants and the admin panel will
    # generate a UI to handle them. There is a set of controller helpers available
    # as well that allows checking for those permissions.
    attribute :actions, Array[String]

    # The name of the class that handles the permissions for this resource. It will
    # probably have the form of `Decidim::<MyComponent>::Permissions`.
    attribute :permissions_class_name, String, default: "Decidim::DefaultPermissions"

    validates :model_class_name, :route_name, :name, presence: true

    # Finds an ActiveRecord::Relation of the resource `model_class`, scoped to the
    # given component. This way you can find resources from another engine without
    # actually coupling both engines. If no `component_manifest` is set for this
    # manifest, it returns an empty collection.
    #
    # component - a Decidim::Component
    #
    # Returns an ActiveRecord::Relation.
    def resource_scope(component)
      return model_class.none unless component_manifest

      component_ids = Decidim::Component.where(participatory_space: component.participatory_space, manifest_name: component_manifest.name).pluck(:id)
      return model_class.none if component_ids.empty?

      model_class.joins(:component).where(decidim_components: { id: component_ids })
    end

    # Finds the current class with the given `model_class_name`
    # in order to avoid problems with Rails' autoloading.
    #
    # Returns a class.
    def model_class
      model_class_name.constantize
    end

    # Public: Finds the permission class from its name, using the
    # `permissions_class_name` attribute. If the class does not exist,
    # it raises an exception. If the class name is not set, it returns nil.
    #
    # Returns a Class.
    def permissions_class
      permissions_class_name&.constantize
    end

    # The name of the named Rails route to create the url to the resource.
    #
    # Returns a String.
    def route_name
      super || model_class_name.demodulize.underscore
    end
  end
end
