function valid_cpf(cpf)
    if #cpf ~= 11 then
        local cpf_length_err = {
            is_valid = false,
            error_message = "Invalid CPF length"
        }
        return cpf_length_err;
    end

    if tonumber(cpf) == nil then
         local cpf_nan_err = {
            is_valid = false,
            error_message = "CPF can only consist of digits"
        }
        return cpf_nan_err;
    end

    local cpf_numerico = tostring(cpf):gsub("%D", "")

    local function calcular_digito(base)
        local soma = 0
        local multiplicador = #base + 1

        for i = 1, #base do
            soma = soma + (tonumber(base:sub(i, i)) * multiplicador)
            multiplicador = multiplicador - 1
        end

        local resto = soma % 11
        return (resto < 2) and 0 or (11 - resto)
    end


    local base_d1 = cpf_numerico:sub(1, 9)
    local digito1_calculado = calcular_digito(base_d1)

    local base_d2 = base_d1 .. tostring(digito1_calculado)
    local digito2_calculado = calcular_digito(base_d2)

    local digito1_original = tonumber(cpf_numerico:sub(10, 10))
    local digito2_original = tonumber(cpf_numerico:sub(11, 11))

    local is_digit_valid = (digito1_calculado == digito1_original) and (digito2_calculado == digito2_original)

    if is_digit_valid then 
        local success_message = {
            is_valid = true,
            error_message = ""
        }
        return success_message
    else
        local cpf_invalid_digit_err = {
            is_valid = false,
            error_message = "Invalid CPF digit validator"
        }
        return cpf_invalid_digit_err
    end

end

function valid_data(date_str)
  if type(date_str) ~= "string" then
    return false, "entrada não é string"
  end

  -- exige exatamente 4 dígitos-'-'2 dígitos-'-'2 dígitos
  if not date_str:match("^%d%d%d%d%-%d%d%-%d%d$") then
    return false, "formato inválido (deve ser YYYY-MM-DD)"
  end

  local y_s, m_s, d_s = date_str:match("^(%d%d%d%d)%-(%d%d)%-(%d%d)$")
  local y, m, d = tonumber(y_s), tonumber(m_s), tonumber(d_s)

  if not y or not m or not d then
    return false, "parte numérica inválida"
  end

  if m < 1 or m > 12 then
    return false, "mês inválido"
  end

  local mdays = {31,28,31,30,31,30,31,31,30,31,30,31}

  if d < 1 or d > mdays[m] then
    return false, "dia inválido para o mês/ano informados"
  end

  return true
end


function iso_to_br(date_str)
  if type(date_str) ~= "string" then
    return nil, "entrada não é string"
  end

  -- captura ano, mês, dia
  local y, m, d = date_str:match("^(%d%d%d%d)%-(%d%d)%-(%d%d)$")
  if not y then
    return nil, "formato inválido, esperado YYYY-MM-DD"
  end

  return string.format("%s/%s/%s", d, m, y)
end