local insert
insert = table.insert
local unpack = unpack or table.unpack
local coroutine = require("lapis.coroutine")
local validate_functions = {
  exists = function(input)
    return input and input ~= "", "%s must be provided"
  end,
  file_exists = function(input)
    return type(input) == "table" and input.filename ~= "" and input.content ~= "", "Missing file"
  end,
  min_length = function(input, len)
    return #tostring(input or "") >= len, "%s must be at least " .. tostring(len) .. " chars"
  end,
  max_length = function(input, len)
    return #tostring(input or "") <= len, "%s must be at most " .. tostring(len) .. " chars"
  end,
  is_file = function(input)
    return type(input) == "table" and input.filename ~= "" and input.content ~= "", "Missing file"
  end,
  is_integer = function(input)
    return tostring(input):match("^%d+$"), "%s must be an integer"
  end,
  is_color = (function()
    local hex = "[a-fA-f0-9]"
    local three = "^#" .. tostring(hex:rep(3)) .. "$"
    local six = "^#" .. tostring(hex:rep(6)) .. "$"
    return function(input)
      input = tostring(input)
      return input:match(three) or input:match(six), "%s must be a color"
    end
  end)(),
  is_timestamp = function(input)
    local month = input and input:match("^%d+%-(%d+)%-(%d+)%s+(%d+):(%d+):(%d+)$")
    return month ~= nil, "%s is not a valid timestamp"
  end,
  matches_pattern = function(input, pattern)
    local match = type(input) == "string" and input:match(pattern) or nil
    return match ~= nil, "%s is not the right format"
  end,
  equals = function(input, value)
    return input == value, "%s must match"
  end,
  one_of = function(input, ...)
    local choices = {
      ...
    }
    for _index_0 = 1, #choices do
      local choice = choices[_index_0]
      if input == choice then
        return true
      end
    end
    return false, "%s must be one of " .. tostring(table.concat(choices, ", "))
  end,
  type = function(input, kind)
    if type(input) == kind then
      return true
    end
    return false, "%s must be a " .. kind
  end,
  test = function(input, fn)
    return fn(input)
  end
}
local test_input
test_input = function(input, func, args)
  local fn = assert(validate_functions[func], "Missing validation function " .. tostring(func))
  if type(args) ~= "table" then
    args = {
      args
    }
  end
  return fn(input, unpack(args))
end
local validate
validate = function(object, validations, opts)
  if opts == nil then
    opts = { }
  end
  local errors = { }
  for _index_0 = 1, #validations do
    local _continue_0 = false
    repeat
      local v = validations[_index_0]
      local key = v[1]
      local error_msg = v[2]
      local input = object[key]
      if v.optional then
        if not (validate_functions.exists(input)) then
          _continue_0 = true
          break
        end
      end
      for fn, args in pairs(v) do
        local _continue_1 = false
        repeat
          if fn == "optional" then
            _continue_1 = true
            break
          end
          if not (type(fn) == "string") then
            _continue_1 = true
            break
          end
          local success, msg = test_input(input, fn, args)
          if not (success) then
            if opts.keys and opts.keys == true then
              errors[key] = (error_msg or msg):format(key)
            else
              insert(errors, (error_msg or msg):format(key))
            end
            break
          end
          _continue_1 = true
        until true
        if not _continue_1 then
          break
        end
      end
      _continue_0 = true
    until true
    if not _continue_0 then
      break
    end
  end
  return next(errors) and errors
end
local assert_valid
assert_valid = function(object, validations)
  if type(validations) == "table" and type(validations.transform) == "function" then
    local result, err_or_state = validations:transform(object)
    if result == nil and err_or_state then
      local errors
      if type(err_or_state) == "string" then
        errors = {
          err_or_state
        }
      else
        errors = err_or_state
      end
      coroutine.yield("error", errors)
      error("assert_valid was not captured: " .. tostring(table.concat(errors, ", ")))
    end
    return result, err_or_state
  end
  local errors = validate(object, validations)
  if errors then
    coroutine.yield("error", errors)
    return error("assert_valid was not captured: " .. tostring(table.concat(errors, ", ")))
  end
end
local with_params
with_params = function(params_spec, fn)
  local types = require("lapis.validate.types")
  local tableshape = require("tableshape")
  local t
  if tableshape.is_type(params_spec) then
    t = types.assert_error(params_spec)
  else
    t = types.params_shape(params_spec):assert_errors()
  end
  return function(self, ...)
    local params, errs_or_state = t:transform(self.params)
    return fn(self, params, errs_or_state, ...)
  end
end
return {
  validate = validate,
  assert_valid = assert_valid,
  test_input = test_input,
  validate_functions = validate_functions,
  with_params = with_params
}
