module Paradocs
  class BasePolicy
    def self.build(name, meth, &block)
      klass = Class.new(self)
      klass.public_send(meth, &block)
      klass.policy_name = name
      klass
    end

    def self.message(&block)
      @message_block = block if block_given?
      @message_block
    end

    def self.validate(&validate_block)
      @validate_block = validate_block if block_given?
      @validate_block
    end

    def self.coerce(&coerce_block)
      @coerce_block = coerce_block if block_given?
      @coerce_block
    end

    def self.eligible(&block)
      @eligible_block = block if block_given?
      @eligible_block
    end

    def self.meta_data(&block)
      @meta_data_block = block if block_given?
      @meta_data_block
    end

    %w(error silent_error).each do |name|
      getter = "#{name}s"
      define_singleton_method(getter) do
        parent_errors = superclass.respond_to?(getter) ? superclass.send(getter) : []
        parent_errors | (instance_variable_get("@#{getter}") || instance_variable_set("@#{getter}", []))
      end

      define_singleton_method("register_#{name}") do |*exceptions| # TODO: spec
        # [Exception, as: :helper_method] or [Exception1, Exception2....]
        only_errors = []
        exceptions.each_with_index do |ex, index|
          if ex.is_a? Hash
            exception = exceptions[index - 1]
            define_method(ex[:as]) { exception }
            next
          end
          next unless ex.is_a?(Class) || ex < StandardError
          only_errors << ex
        end
        instance_variable_set("@#{getter}", ((self.public_send(getter) || []) + only_errors).uniq)
      end

      define_method(getter) do
        instance_variable_set("@#{getter}", self.class.send("register_#{name}") || [])
      end
    end

    def self.policy_name=(name)
      @policy_name = name
    end

    def self.policy_name
      @policy_name || self.name.split("::").last.downcase.to_sym
    end

    attr_accessor :environment
    def initialize(*args)
      @init_params = args
    end

    def eligible?(value, key, payload)
      args = (init_params + [value, key, payload])
      (self.class.eligible || ->(*) { true }).call(*args)
    end

    def coerce(value, key, context)
      (self.class.coerce || ->(v, *_) { v }).call(value, key, context)
    end

    def valid?(value, key, payload)
      args = (init_params + [value, key, payload])
      @message = self.class.message.call(*args) if self.class.message
      validate(*args)
    end

    def meta_data
      return self.class.meta_data.call(*init_params) if self.class.meta_data
      meta
    end

    def validate(*args)
      (self.class.validate || ->(*) { true }).call(*args)
    end

    def policy_name
      (self.class.policy_name || self.to_s.demodulize.underscore).to_sym
    end

    def message
      @message ||= 'is invalid'
    end

    protected

    def validate(*args)
      (self.class.validate || ->(*args) { true }).call(*args)
    end

    private

    def meta
      @meta = {self.class.policy_name => {errors: self.class.errors}}
    end

    def init_params
      @init_params ||= [] # safe default if #initialize was overwritten
    end
  end
end