require "luassert"
local namespaces = require "luassert.namespaces"

local reserved_words = {
  "and",
  "break",
  "do",
  "else",
  "elseif",
  "end",
  "false",
  "for",
  "function",
  "if",
  "in",
  "local",
  "nil",
  "not",
  "or",
  "repeat",
  "return",
  "then",
  "true",
  "until",
  "while",
}

local content = {
  "---@meta",
  "---This file is autogenerated, DO NOT EDIT",
  'error "Cannot require a meta file"',
  "",
  "---@generic T:any",
  "---@alias LuassertFunction fun(value:T, message?:string):T",
  "---@alias LuassertFunctionTwoArgs fun(expected:T, actual:T, message?:string):T",
  "---@alias LuassertFunctionMultiArgs fun(...:T):T",
  "",
  "---@class Luassert",
}

local args_count_per_matcher = {
  matches = 2,
  match = 2,
  equal = 2,
  equals = 2,
  same = 2,
  error_match = 2,
  error_matches = 2,
  matches_error = 2,
  match_error = 2,
  near = 3,
}

local get_type_by_args = function(assertion)
  local arg_count = args_count_per_matcher[assertion] or 1
  if arg_count == 1 then
    return "LuassertFunction"
  end
  if arg_count == 2 then
    return "LuassertFunctionTwoArgs"
  end
  return "LuassertFunctionMultiArgs"
end

local fix_reserved_words = function(word)
  if vim.tbl_contains(reserved_words, word) then
    return table.concat { word:sub(1, 1):upper(), word:sub(2) }
  end
  return word
end

local assertions = vim.tbl_keys(namespaces.assertion)
local modifiers = vim.tbl_keys(namespaces.modifier)

table.sort(assertions)
table.sort(modifiers)

for _, modifier in ipairs(modifiers) do
  for _, assertion in ipairs(assertions) do
    table.insert(content, ("---@field %s_%s %s"):format(modifier, assertion, get_type_by_args(assertion)))
  end
end

for _, assertion in ipairs(assertions) do
  table.insert(content, ("---@field %s %s"):format(fix_reserved_words(assertion), get_type_by_args(assertion)))
end

for _, modifier in ipairs(modifiers) do
  table.insert(content, ("---@field %s Luassert"):format(fix_reserved_words(modifier)))
end

local fd = assert(vim.loop.fs_open("./lua/plenary/_meta/_luassert.lua", "w", 438))
assert(vim.loop.fs_write(fd, table.concat(content, "\n"), 0))
assert(vim.loop.fs_close(fd))
