# frozen_string_literal: true

require 'roda/endpoints'
require 'roda/endpoints/endpoint/class_interface'
require 'roda/endpoints/endpoint/lookup'
require 'roda/endpoints/endpoint/namespace'
require 'roda/endpoints/endpoint/transactions'
require 'roda/endpoints/endpoint/validations'
require 'roda/endpoints/endpoint/verbs'
require 'dry/monads'

class Roda
  module Endpoints
    # Generic HTTP endpoint abstraction.
    class Endpoint
      extend ClassInterface
      include Dry::Monads::Either::Mixin

      autoload :Collection, 'roda/endpoints/endpoint/collection'
      autoload :Item, 'roda/endpoints/endpoint/item'
      autoload :Singleton, 'roda/endpoints/endpoint/singleton'

      self.attributes = %i(container type).freeze
      self.defaults = EMPTY_HASH
      self.statuses = {
        get: { success: :ok, failure: :not_found },
        put: { success: :accepted, failure: :unprocessable_entity },
        post: { success: :created, failure: :unprocessable_entity },
        patch: { success: :accepted, failure: :unprocessable_entity },
        delete: { success: :no_content, failure: :unprocessable_entity }
      }.freeze

      self.verbs = EMPTY_SET

      # @param attributes [{Symbol=>Object}]
      def initialize(**attributes)
        self.class.defaults.merge(attributes).each do |key, value|
          singleton_class.define_attribute(key) unless respond_to?(key)
          instance_variable_set(:"@#{key}", value)
        end
      end

      prepend Namespace
      prepend Verbs
      prepend Validations
      include Transactions
      prepend Lookup

      attr_accessor :captures

      # @return [Symbol]
      def type
        self.class.type
      end

      # @param type [:collection, :item]
      # @param attributes [{Symbol=>Object}]
      def with(type: self.class, **attributes)
        type.new to_hash.merge(attributes).merge(inheritable_attributes)
      end

      # @param [Class(Endpoint)] type
      # @param [Hash] params
      def child(type: Singleton, **params)
        with(type: type, **params)
      end

      # @return [{Symbol=>Object}]
      def inheritable_attributes
        { parent: self, container: container }
      end

      # @return [Proc]
      def route
        prepare_validations!
        prepare_verbs!
        self.class.route
      end

      # @return [{Symbol=>Object}]
      def to_hash
        self.class.attributes.each_with_object({}) do |name, hash|
          hash[name] = public_send(name)
        end
      end
    end
  end
end