# frozen_string_literal: true

require 'roda/endpoints/endpoint'
require 'roda/endpoints/endpoint/data'
require 'roda/endpoints/endpoint/caching'

class Roda
  module Endpoints
    class Endpoint
      # HTTP endpoint representing a specific item of collection uniquely
      # identified by some parameter.
      class Singleton < Roda::Endpoints::Endpoint
        prepend Data
        prepend Caching

        self.attributes += %i(finder last_modified)
        self.defaults = defaults.merge(last_modified: :updated_at)

        # @return [Symbol]
        attr_reader :finder

        # @return [ROM::Struct]
        def entity
          @entity ||= fetch_entity
        end

        attr_writer :entity

        # @return [ROM::Struct]
        def fetch_entity
          instance_exec(&finder)
        end

        # @return [Time]
        def last_modified
          @last_modified ? entity.public_send(@last_modified) : super
        end

        route do |r, endpoint|
          endpoint.verbs.each do |verb|
            # @route #{verb} /{collection.name}/{id}
            r.public_send(verb, transaction: verb)
          end
        end

        transaction :get do |endpoint|
          step :retrieve, with: endpoint.operation_for(:get)
        end

        transaction :patch do |endpoint|
          step :validate, with: endpoint.validation_for(:patch)
          step :persist, with: endpoint.operation_for(:patch)
        end

        transaction :put do |endpoint|
          step :validate, with: endpoint.validation_for(:put)
          # step :reset, with: 'endpoints.operations.reset'
          step :persist, with: endpoint.operation_for(:put)
        end

        transaction :delete do |endpoint|
          step :validate, with: endpoint.validation_for(:delete)
          step :persist, with: endpoint.operation_for(:delete)
        end

        # @route GET /{collection.name}/{id}
        # @!method get(params)
        #   @param [Hash] params
        #   @return [Dry::Monads::Either]
        verb :get do |_params|
          Right(entity)
        end

        # @route PATCH /{collection.name}/{id}
        # @!method patch(params)
        #   @param [Hash] params
        #   @return [Dry::Monads::Either]
        verb :patch do |params|
          changeset = params[name]
          Right(repository.update(id, changeset))
        end

        # @route PUT /{collection.name}/{id}
        # @!method put(params)
        #   @param [Hash] params
        #   @return [Dry::Monads::Either]
        verb :put do |params|
          changeset = entity.to_hash.keys.each_with_object({}) do |key, changes|
            changes[key] = nil
          end.merge(params[name] || {})
          Right(repository.update(id, changeset))
        end

        # @route DELETE /{collection.name}/{id}
        # @!method delete(params)
        #   @param [Hash] params
        #   @return [Dry::Monads::Either]
        verb :delete do |_params|
          if (result = repository.delete(id))
            Right(nil)
          else
            Left(result)
          end
        end
      end
    end
  end
end