local c_params = {
    retrieve = "first",
    node_type = "parameter_list",
    subtree = {
        {
            retrieve = "all",
            node_type = "parameter_declaration|variadic_parameter_declaration|optional_parameter_declaration",
            subtree = {
                { retrieve = "first", recursive = true, node_type = "identifier", extract = true },
            },
        },
    },
}
local c_function_extractor = function(node)
    local tree = {
        {
            retrieve = "first",
            node_type = "template_parameter_list",
            subtree = {
                {
                    retrieve = "first",
                    node_type = "type_parameter_declaration|variadic_type_parameter_declaration",
                    subtree = {
                        { retrieve = "all", node_type = "type_identifier", extract = true },
                    },
                },
            },
        },
        {
            retrieve = "first",
            node_type = "function_declarator",
            subtree = {
                c_params,
            },
        },
        {
            retrieve = "first",
            recursive = true,
            node_type = "function_declarator",
            extract = true,
        },
        {
            retrieve = "first",
            node_type = "compound_statement",
            subtree = {
                { retrieve = "first", node_type = "return_statement", extract = true },
            },
        },
        {
            retrieve = "first",
            node_type = "type_identifier",
            extract = true,
        },
        {
            retrieve = "first",
            node_type = "primitive_type",
            extract = true,
        },
        {
            retrieve = "first",
            node_type = "pointer_declarator",
            extract = true,
        },
        c_params,
    }

    local nodes = neogen.utilities.nodes:matching_nodes_from(node, tree)
    local res = neogen.utilities.extractors:extract_from_matched(nodes)

    if nodes.function_declarator then
        local subnodes = neogen.utilities.nodes:matching_nodes_from(nodes.function_declarator[1]:parent(), tree)
        local subres = neogen.utilities.extractors:extract_from_matched(subnodes)
        res = vim.tbl_deep_extend("keep", res, subres)
    end

    --- Checks if we have a return statement.
    --- If so, return { true } as we don't need to properly know the content of the node for the template
    --- @return table?
    local has_return_statement = function()
        if res.primitive_type and res.primitive_type[1] == "void" then
            return
        end
        if res.return_statement then
            -- function implementation
            return res.return_statement
        elseif res.function_declarator and (res.primitive_type or res.type_identifier) then
            if res.type_identifier then
                return { true }
            elseif res.primitive_type and res.primitive_type[1] ~= "void" or res.pointer_declarator then
                -- function prototype
                return { true }
            end
        end
        -- not found
        return nil
    end

    if node:type() == "template_declaration" then
        res.tparam = res.type_identifier
    end

    return {
        parameters = res.identifier,
        type_identifier = res.type_identifier,
        tparam = res.tparam,
        return_statement = has_return_statement(),
    }
end

local c_config = {
    parent = {
        func = {
            "function_declaration",
            "function_definition",
            "declaration",
            "field_declaration",
            "template_declaration",
        },
        file = { "translation_unit" },
    },

    data = {
        func = {
            ["function_declaration|function_definition|declaration|field_declaration|template_declaration"] = {
                ["0"] = {
                    extract = c_function_extractor,
                },
            },
        },
        file = {
            ["translation_unit"] = {
                ["0"] = {
                    extract = function()
                        return {}
                    end,
                },
            },
        },
    },

    locator = function(node_info, nodes_to_match)
        local result = neogen.default_locator(node_info, nodes_to_match)
        if not result then
            return nil
        end

        if node_info.current == nil then
            return result
        end

        -- if the function happens to be a function template we want to place
        -- the annotation before the template statement and extract the
        -- template parameters names as well
        if node_info.current:parent() == nil then
            return result
        end
        if node_info.current:parent():type() == "template_declaration" then
            return result:parent()
        end
        return result
    end,

    -- Use default granulator and generator
    granulator = nil,
    generator = nil,

    template = {
        annotation_convention = "doxygen",
        use_default_comment = false,

        doxygen = {
            { nil, "/**", { no_results = true, type = { "func", "file" } } },
            { nil, " * @file", { no_results = true, type = { "file" } } },
            { nil, " * @brief $1", { no_results = true, type = { "func", "file" } } },
            { nil, " */", { no_results = true, type = { "func", "file" } } },
            { nil, "", { no_results = true, type = { "file" } } },

            { nil, "/**", { type = { "func" } } },
            { nil, " * @brief $1", { type = { "func" } } },
            { nil, " *", { type = { "func" } } },
            { "tparam", " * @tparam %s $1" },
            { "parameters", " * @param %s $1" },
            { "return_statement", " * @return $1" },
            { nil, " */" },
        },
    },
}

local cpp_config = {
    parent = {
        class = { "class_specifier" },
    },
    data = {
        class = {
            ["class_specifier"] = {
                ["0"] = {
                    extract = function(node)
                        local tree = {
                            { retrieve = "first", node_type = "type_identifier", extract = true },
                        }
                        local nodes = neogen.utilities.nodes:matching_nodes_from(node, tree)
                        local res = neogen.utilities.extractors:extract_from_matched(nodes)
                        return res
                    end,
                },
            },
        },
    },
    template = {

        doxygen = {
            { nil, "/**", { no_results = true, type = { "func", "file", "class" } } },
            { nil, " * @file", { no_results = true, type = { "file" } } },
            { nil, " * @brief $1", { no_results = true, type = { "func", "file", "class" } } },
            { nil, " */", { no_results = true, type = { "func", "file", "class" } } },
            { nil, "", { no_results = true, type = { "file" } } },

            { nil, "/**", { type = { "func", "class" } } },
            { "type_identifier", " * @class %s", { type = { "class" } } },
            { nil, " * @brief $1", { type = { "func", "class" } } },
            { nil, " *", { type = { "func", "class" } } },
            { "tparam", " * @tparam %s $1" },
            { "parameters", " * @param %s $1" },
            { "return_statement", " * @return $1" },
            { nil, " */", { type = { "func", "class" } } },
        },
    },
}
cpp_config = vim.tbl_deep_extend("force", c_config, cpp_config)

return {
    c_config = c_config,
    cpp_config = cpp_config,
}
