local files      = require 'files'
local searcher   = require 'core.searcher'
local vm         = require 'vm'
local hoverLabel = require 'core.hover.label'
local hoverDesc  = require 'core.hover.description'
local guide      = require 'parser.guide'
local lookback   = require 'core.look-backward'

local function findNearCall(uri, ast, pos)
    local text = files.getText(uri)
    local nearCall
    guide.eachSourceContain(ast.ast, pos, function (src)
        if src.type == 'call'
        or src.type == 'table'
        or src.type == 'function' then
            -- call(),$
            if  src.finish <= pos
            and text:sub(src.finish, src.finish) == ')' then
                return
            end
            -- {},$
            if  src.finish <= pos
            and text:sub(src.finish, src.finish) == '}' then
                return
            end
            if not nearCall or nearCall.start <= src.start then
                nearCall = src
            end
        end
    end)
    if not nearCall then
        return nil
    end
    if nearCall.type ~= 'call' then
        return nil
    end
    return nearCall
end

local function makeOneSignature(source, oop, index)
    local label = hoverLabel(source, oop)
    -- 去掉返回值
    label = label:gsub('%s*->.+', '')
    local params = {}
    local i = 0
    local argStart, argLabel = label:match '()(%b())'
    local converted = argLabel
        : sub(2, -2)
        : gsub('%b<>', function (str)
            return ('_'):rep(#str)
        end)
        : gsub('%b()', function (str)
            return ('_'):rep(#str)
        end)
        : gsub('[%[%]%(%)]', '_')
    for start, finish in converted:gmatch '%s*()[^,]+()' do
        i = i + 1
        params[i] = {
            label = {start + argStart, finish - 1 + argStart},
        }
    end
    -- 不定参数
    if index > i and i > 0 then
        local lastLabel = params[i].label
        local text = label:sub(lastLabel[1], lastLabel[2])
        if text == '...' then
            index = i
        end
    end
    return {
        label       = label,
        params      = params,
        index       = index,
        description = hoverDesc(source),
    }
end

local function makeSignatures(text, call, pos)
    local node = call.node
    local oop = node.type == 'method'
             or node.type == 'getmethod'
             or node.type == 'setmethod'
    local index
    if call.args then
        local args = {}
        for _, arg in ipairs(call.args) do
            if not arg.dummy then
                args[#args+1] = arg
            end
        end
        for i, arg in ipairs(args) do
            local start =  lookback.findTargetSymbol(text, arg.start - 1, '(')
                        or lookback.findTargetSymbol(text, arg.start - 1, ',')
                        or arg.start
            if start > pos then
                index = i - 1
                break
            end
            if pos <= arg.finish then
                index = i
                break
            end
        end
        if not index then
            local backSymbol = lookback.findSymbol(text, pos)
            if backSymbol == ','
            or backSymbol == '(' then
                index = #args + 1
            else
                index = #args
            end
        end
    else
        index = 1
    end
    local signs = {}
    local defs = vm.getDefs(node)
    local mark = {}
    for _, src in ipairs(defs) do
        src = searcher.getObjectValue(src) or src
        if src.type == 'function'
        or src.type == 'doc.type.function' then
            if not mark[src] then
                mark[src] = true
                signs[#signs+1] = makeOneSignature(src, oop, index)
            end
        end
    end
    return signs
end

return function (uri, pos)
    local ast = files.getState(uri)
    if not ast then
        return nil
    end
    local text = files.getText(uri)
    pos = lookback.skipSpace(text, pos)
    local call = findNearCall(uri, ast, pos)
    if not call then
        return nil
    end
    local signs = makeSignatures(text, call, pos)
    if not signs or #signs == 0 then
        return nil
    end
    return signs
end
