local files      = require 'files'
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 state = files.getState(uri)
    if not state or not text then
        return nil
    end
    local nearCall
    guide.eachSourceContain(ast.ast, pos, function (src)
        if src.type == 'call'
        or src.type == 'table'
        or src.type == 'function' then
            local finishOffset = guide.positionToOffset(state, src.finish)
            -- call(),$
            if  src.finish <= pos
            and text:sub(finishOffset, finishOffset) == ')' then
                return
            end
            -- {},$
            if  src.finish <= pos
            and text:sub(finishOffset, finishOffset) == '}' 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

---@async
local function makeOneSignature(source, oop, index)
    local label = hoverLabel(source, oop)
    if not label then
        return nil
    end
    -- 去掉返回值
    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 - 1, finish - 1 + argStart},
        }
    end
    -- 不定参数
    if index > i and i > 0 then
        local lastLabel = params[i].label
        local text = label:sub(lastLabel[1] + 1, lastLabel[2])
        if text:sub(1, 3) == '...' then
            index = i
        end
    end
    return {
        label       = label,
        params      = params,
        index       = index,
        description = hoverDesc(source),
    }
end

---@async
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 arg.type ~= 'self' then
                args[#args+1] = arg
            end
        end
        local uri   = guide.getUri(call)
        local state = files.getState(uri)
        for i, arg in ipairs(args) do
            local startOffset = guide.positionToOffset(state, arg.start)
            startOffset =  lookback.findTargetSymbol(text, startOffset, '(')
                        or lookback.findTargetSymbol(text, startOffset, ',')
                        or startOffset
            local startPos = guide.offsetToPosition(state, startOffset)
            if startPos > pos then
                index = i - 1
                break
            end
            if pos <= arg.finish then
                index = i
                break
            end
        end
        if not index then
            local offset     = guide.positionToOffset(state, pos)
            local backSymbol = lookback.findSymbol(text, offset)
            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
        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

---@async
return function (uri, pos)
    local state = files.getState(uri)
    local text  = files.getText(uri)
    if not state or not text then
        return nil
    end
    local offset = guide.positionToOffset(state, pos)
    pos = guide.offsetToPosition(state, lookback.skipSpace(text, offset))
    local call = findNearCall(uri, state, 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
