local fs       = require 'bee.filesystem'
local config   = require 'config'
local util     = require 'utility'
local await    = require 'await'
local progress = require 'progress'
local lang     = require 'language'

local m = {}

---@class meta
---@field root    string
---@field classes meta.class[]

---@class meta.class
---@field name        string
---@field comment     string
---@field location    string
---@field namespace   string
---@field baseClass   string
---@field attribute   string
---@field integerface string[]
---@field fields      meta.field[]
---@field methods     meta.method[]

---@class meta.field
---@field name     string
---@field typeName string
---@field comment  string
---@field location string

---@class meta.method
---@field name           string
---@field comment        string
---@field location       string
---@field isStatic       boolean
---@field returnTypeName string
---@field params         {name: string, typeName: string}[]

---@param ... string
---@return string
local function mergeString(...)
    local buf = {}
    for i = 1, select('#', ...) do
        local str = select(i, ...)
        if str ~= '' then
            buf[#buf+1] = str
        end
    end
    return table.concat(buf, '.')
end

local function addComments(lines, comment)
    if comment == '' then
        return
    end
    lines[#lines+1] = '--'
    lines[#lines+1] = '--' .. comment:gsub('[\r\n]+$', ''):gsub('\n', '\n--')
    lines[#lines+1] = '--'
end

---@param lines string[]
---@param name string
---@param method meta.method
local function addMethod(lines, name, method)
    if not method.name:match '^[%a_][%w_]*$' then
        return
    end
    addComments(lines, method.comment)
    lines[#lines+1] = ('---@source %s'):format(method.location:gsub('#', ':'))
    local params = {}
    for _, param in ipairs(method.params) do
        lines[#lines+1] = ('---@param %s %s'):format(param.name, param.typeName)
        params[#params+1] = param.name
    end
    if  method.returnTypeName ~= ''
    and method.returnTypeName ~= 'Void' then
        lines[#lines+1] = ('---@return %s'):format(method.returnTypeName)
    end
    lines[#lines+1] = ('function %s%s%s(%s) end'):format(
        name,
        method.isStatic and ':' or '.',
        method.name,
        table.concat(params, ', ')
    )
    lines[#lines+1] = ''
end

---@param root string
---@param class meta.class
---@return string
local function buildText(root, class)
    local lines = {}

    addComments(lines, class.comment)
    lines[#lines+1] = ('---@source %s'):format(class.location:gsub('#', ':'))
    if class.baseClass == '' then
        lines[#lines+1] = ('---@class %s'):format(mergeString(class.namespace, class.name))
    else
        lines[#lines+1] = ('---@class %s: %s'):format(mergeString(class.namespace, class.name), class.baseClass)
    end

    for _, field in ipairs(class.fields) do
        addComments(lines, field.comment)
        lines[#lines+1] = ('---@source %s'):format(field.location:gsub('#', ':'))
        lines[#lines+1] = ('---@field %s %s'):format(field.name, field.typeName)
    end

    lines[#lines+1] = ('---@source %s'):format(class.location:gsub('#', ':'))
    local name = mergeString(root, class.namespace, class.name)
    lines[#lines+1] = ('%s = {}'):format(name)
    lines[#lines+1] = ''

    for _, method in ipairs(class.methods) do
        addMethod(lines, name, method)
    end

    return table.concat(lines, '\n')
end

local function buildRootText(api)
    local lines = {}

    lines[#lines+1] = ('---@class %s'):format(api.root)
    lines[#lines+1] = ('%s = {}'):format(api.root)
    lines[#lines+1] = ''
    return table.concat(lines, '\n')
end

---@async
---@param path string
---@param api meta
function m.build(path, api)

    local files = util.multiTable(2, function ()
        return { '---@meta' }
    end)

    files[api.root][#files[api.root]+1] = buildRootText(api)

    local proc <close> = progress.create(nil, lang.script.WINDOW_PROCESSING_BUILD_META, 0.5)
    for i, class in ipairs(api.classes) do
        local space = class.namespace ~= '' and class.namespace or api.root
        proc:setMessage(space)
        proc:setPercentage(i / #api.classes * 100)
        local text = buildText(api.root, class)
        files[space][#files[space]+1] = text
        await.delay()
    end

    for space, texts in pairs(files) do
        util.saveFile(path .. '/' .. space .. '.lua', table.concat(texts, '\n\n'))
    end
end

return m
