local pairs = pairs
local error = error
local setmetatable = setmetatable

local all = require("validation.rules.all")
local context = require("validation.context")
local message = require("validation.message")

local default_properties = {}
local last_messager = nil
local messager = error
local validation = {}

function validation.set_messager(callback)
    last_messager = messager
    messager = callback
end

function validation.restore_messager(_)
    messager = last_messager or error
    last_messager = nil
end

function validation.set_defaults(properties)
    for key, value in pairs(properties) do
        default_properties[key] = value
    end
end

function validation:assert(input, properties)
    local chain_context

    chain_context = context.new(self, {
        input = input,
    })
    chain_context:merge(properties or {})
    chain_context:merge(default_properties)
    chain_context:apply_rule()

    if not chain_context.result then
        local context_message = message.new(chain_context)

        messager(context_message.get_full())
    end
end

function validation:check(input, properties)
    local chain_context

    chain_context = context.new(self, {
        input = input,
    })
    chain_context:merge(properties or {})
    chain_context:merge(default_properties)

    for _, rule in pairs(self.get_rules()) do
        local child_context = chain_context:new_child(rule)
        child_context:apply_rule()

        if not child_context.result then
            local child_message = message.new(child_context)

            messager(child_message.get_single())
            break
        end
    end
end

function validation:validate(input, properties)
    local chain_context

    chain_context = context.new(self, {
        input = input,
    })
    chain_context:merge(properties or {})
    chain_context:merge(default_properties)
    chain_context:apply_rule()

    return chain_context.result
end

function validation.new()
    local new_validation = all()
    local metatable = {}

    for key, value in pairs(validation) do
        new_validation[key] = value
    end

    function metatable:__index(key)
        self._last = require("validation.rules." .. key) -- Use pcall to other prefices

        return self
    end

    function metatable:__call(...)
        self:add_rule(self._last(...))

        return self
    end

    return setmetatable(new_validation, metatable)
end

return setmetatable(validation, {
    __index = function(self, key)
        local new_validation = validation.new()

        return new_validation[key]
    end,
})
