local ts = vim.treesitter
local ls = require('luasnip')
local d = ls.dynamic_node
local i = ls.insert_node
local s = ls.snippet_node
local t = ls.text_node

local helper = require('typstar.autosnippets')
local utils = require('typstar.utils')
local cfg = require('typstar.config').config.snippets
local math = helper.in_math
local snip = helper.snip

local snippets = {}
local visual_disable = {}
local visual_disable_normal = {}
local visual_disable_postfix = {}
utils.generate_bool_set(cfg.visual_disable, visual_disable)
utils.generate_bool_set(cfg.visual_disable_normal, visual_disable_normal)
utils.generate_bool_set(cfg.visual_disable_postfix, visual_disable_postfix)

local operations = { -- first boolean: existing brackets should be kept; second boolean: brackets should be added
    { 'vi', '1/', '', true, false },
    { 'bb', '(', ')', true, false }, -- add round brackets
    { 'sq', '[', ']', true, false }, -- add square brackets
    { 'st', '{', '}', true, false }, -- add curly brackets
    { 'bB', '(', ')', false, false }, -- replace with round brackets
    { 'sQ', '[', ']', false, false }, -- replace with square brackets
    { 'BB', '', '', false, false }, -- remove brackets
    { 'ss', '"', '"', false, false },
    { 'agl', 'lr(angle.l ', ' angle.r)', false, false },
    { 'abs', 'abs', '', true, true },
    { 'ul', 'underline', '', true, true },
    { 'ol', 'overline', '', true, true },
    { 'ub', 'underbrace', '', true, true },
    { 'ob', 'overbrace', '', true, true },
    { 'ht', 'hat', '', true, true },
    { 'br', 'macron', '', true, true },
    { 'dt', 'dot', '', true, true },
    { 'ci', 'circle', '', true, true },
    { 'td', 'tilde', '', true, true },
    { 'nr', 'norm', '', true, true },
    { 'vv', 'vec', '', true, true },
    { 'rt', 'sqrt', '', true, true },
    { 'flo', 'floor', '', true, true },
    { 'cei', 'ceil', '', true, true },
}

local ts_wrap_query = ts.query.parse('typst', '[(call) (ident) (letter) (number)] @wrap')
local ts_wrapnobrackets_query = ts.query.parse('typst', '(group) @wrapnobrackets')

local process_ts_query = function(bufnr, cursor, query, root, insert1, insert2, cut_offset)
    for _, match in ipairs(utils.treesitter_iter_matches(root, query, bufnr, cursor[1], cursor[1] + 1)) do
        for _, nodes in pairs(match) do
            local start_row, start_col, end_row, end_col = utils.treesitter_match_start_end(nodes)
            if end_row == cursor[1] and end_col == cursor[2] then
                vim.schedule(function() -- to not interfere with luasnip
                    local cursor_offset = 0
                    local old_len1, new_len1 = utils.insert_text(bufnr, start_row, start_col, insert1, 0, cut_offset)
                    if start_row == cursor[1] then cursor_offset = cursor_offset + (new_len1 - old_len1) end
                    local old_len2, new_len2 =
                        utils.insert_text(bufnr, end_row, cursor[2] + cursor_offset, insert2, cut_offset, 0)
                    if end_row == cursor[1] then cursor_offset = cursor_offset + (new_len2 - old_len2) end
                    vim.api.nvim_win_set_cursor(0, { cursor[1] + 1, cursor[2] + cursor_offset })
                end)
                return true
            end
        end
    end
    return false
end

local smart_wrap = function(args, parent, old_state, expand)
    local bufnr = vim.api.nvim_get_current_buf()
    local cursor = utils.get_cursor_pos()
    local root = utils.get_treesitter_root(bufnr)

    local trigger = expand[1]
    local expand1 = expand[5] and expand[2] .. '(' or expand[2]
    local expand2 = expand[5] and expand[3] .. ')' or expand[3]

    -- visual selection
    if not visual_disable[trigger] and #parent.env.LS_SELECT_RAW > 0 then
        return s(nil, t(expand1 .. table.concat(parent.env.LS_SELECT_RAW) .. expand2))
    end

    -- postfix
    if not visual_disable_postfix[trigger] then
        if
            process_ts_query(bufnr, cursor, ts_wrapnobrackets_query, root, expand[2], expand[3], expand[4] and 0 or 1)
            or process_ts_query(bufnr, cursor, ts_wrap_query, root, expand1, expand2)
        then
            return s(nil, t())
        end
    end

    -- normal snippet
    if not visual_disable_normal[trigger] then
        return s(nil, { t(expand1), i(1, '1+1'), t(expand2) })
    else
        return s(nil, t(trigger))
    end
end

for _, val in pairs(operations) do
    table.insert(
        snippets,
        snip(val[1], '<>', { d(1, smart_wrap, {}, { user_args = { val } }) }, math, 1500, { wordTrig = false })
    )
end

return {
    unpack(snippets),
}
