local M = {}

-- Navigation history stack
M.history = {}
M.history_index = 0

-- Setup function to initialize the plugin
function M.setup(opts)
    opts = opts or {}

    -- Default options
    local config = {
        -- Enable mouse support for tag navigation
        enable_mouse = opts.enable_mouse ~= false,
        -- Maximum history size
        max_history = opts.max_history or 100,
    }

    -- Store config
    M.config = config

    -- Initialize history
    M.history = {}
    M.history_index = 0

    -- Set up the mouse mapping
    M.setup_mappings()
end

-- Save current location to history
function M.save_location()
    local bufnr = vim.api.nvim_get_current_buf()
    local cursor = vim.api.nvim_win_get_cursor(0)
    local file = vim.api.nvim_buf_get_name(bufnr)
    local word = vim.fn.expand("<cword>")

    return {
        file = file,
        line = cursor[1],
        col = cursor[2],
        word = word,
        bufnr = bufnr
    }
end

-- Check if we should go back instead of forward
function M.should_go_back(current_location)
    if M.history_index > 0 then
        local prev = M.history[M.history_index]
        -- Check if we're at the location we navigated to from the previous entry
        if prev.target and
           prev.target.file == current_location.file and
           math.abs(prev.target.line - current_location.line) <= 2 then
            -- We're on the same line as where we navigated to
            -- Check if the line contains the original search word
            local line = vim.api.nvim_get_current_line()
            if line:find(prev.source.word, 1, true) then
                return true
            end
        end
    end
    return false
end

-- Navigate back in history
function M.go_back()
    if M.history_index > 0 then
        local entry = M.history[M.history_index]
        M.history_index = M.history_index - 1

        -- Jump to the source location
        if vim.fn.filereadable(entry.source.file) == 1 then
            vim.cmd("edit " .. vim.fn.fnameescape(entry.source.file))
            vim.api.nvim_win_set_cursor(0, {entry.source.line, entry.source.col})
            return true
        end
    end
    return false
end

-- Function to navigate to tag under cursor
function M.goto_tag()
    -- Get current location before navigation
    local current_location = M.save_location()

    -- Get the word under cursor
    local cword = vim.fn.expand("<cword>")

    if not cword or cword == "" then
        vim.notify("No word under cursor", vim.log.levels.WARN)
        return
    end

    -- Check if we should navigate back instead
    if M.should_go_back(current_location) then
        if M.go_back() then
            vim.notify("← Back to " .. vim.fn.fnamemodify(M.history[M.history_index + 1].source.file, ":t"), vim.log.levels.INFO)
            return
        end
    end

    -- Store location before jump
    local source_location = current_location

    -- Check if tags file exists
    local tagfiles = vim.fn.tagfiles()
    local has_tags = tagfiles and #tagfiles > 0
    local navigation_successful = false

    if not has_tags then
        vim.notify("No tags file found. Please generate a tags file with ctags.", vim.log.levels.WARN)
        return
    end

    -- Try to jump to tag using feedkeys to simulate Ctrl-]
    local keys = vim.api.nvim_replace_termcodes("<C-]>", true, false, true)
    local ok = pcall(vim.api.nvim_feedkeys, keys, "n", false)

    if not ok then
        -- If feedkeys fails, try :tag command
        ok = pcall(vim.cmd, "tag " .. cword)
        if not ok then
            vim.notify("Tag not found: " .. cword, vim.log.levels.WARN)
        else
            navigation_successful = true
        end
    else
        navigation_successful = true
    end

    -- If navigation was successful, add to history
    if navigation_successful then
        vim.defer_fn(function()
            local target_location = M.save_location()

            -- Only add to history if we actually moved
            if source_location.file ~= target_location.file or
               math.abs(source_location.line - target_location.line) > 1 then

                -- Truncate history if we're not at the end
                if M.history_index < #M.history then
                    for i = #M.history, M.history_index + 1, -1 do
                        table.remove(M.history, i)
                    end
                end

                -- Add new entry
                table.insert(M.history, {
                    source = source_location,
                    target = target_location
                })

                -- Maintain max history size
                if #M.history > M.config.max_history then
                    table.remove(M.history, 1)
                else
                    M.history_index = #M.history
                end

                vim.notify("→ " .. target_location.word .. " (click again to go back)", vim.log.levels.INFO)
            end
        end, 50)
    end
end

-- Set up mouse mappings
function M.setup_mappings()
    if not M.config or not M.config.enable_mouse then
        return
    end

    local mapping = "<LeftMouse>"

    -- Function to create the mapping
    local function create_mapping(bufnr)
        vim.keymap.set("n", mapping, function()
            -- First, execute the mouse click to move cursor
            local mouse_key = vim.api.nvim_replace_termcodes("<LeftMouse>", true, false, true)
            vim.api.nvim_feedkeys(mouse_key, "nx", false)
            -- Then navigate to tag after a short delay to ensure cursor has moved
            vim.defer_fn(function()
                M.goto_tag()
            end, 10)
        end, { buffer = bufnr, silent = true, desc = "Navigate to tag definition" })
    end

    -- Create buffer-local autocommand to set up mappings in new buffers
    vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, {
        group = vim.api.nvim_create_augroup("NvimCtagTap", { clear = true }),
        callback = function(args)
            create_mapping(args.buf)
        end
    })

    -- Also set up mappings in all existing buffers immediately
    for _, buf in ipairs(vim.api.nvim_list_bufs()) do
        if vim.api.nvim_buf_is_valid(buf) and vim.api.nvim_buf_is_loaded(buf) then
            create_mapping(buf)
        end
    end
end

-- Enable the plugin (convenience function)
function M.enable()
    if not M.config then
        -- If no config exists, do a full setup
        M.setup({ enable_mouse = true })
    else
        -- If config exists, just update the flag and recreate mappings
        M.config.enable_mouse = true
        M.setup_mappings()
    end
end

-- Clear navigation history
function M.clear_history()
    M.history = {}
    M.history_index = 0
    vim.notify("Navigation history cleared", vim.log.levels.INFO)
end

-- Show navigation history
function M.show_history()
    if #M.history == 0 then
        vim.notify("Navigation history is empty", vim.log.levels.INFO)
        return
    end

    local lines = {"Navigation History (current index: " .. M.history_index .. "):", ""}
    for i, entry in ipairs(M.history) do
        local marker = i == M.history_index and "→ " or "  "
        local source_name = vim.fn.fnamemodify(entry.source.file, ":t")
        local target_name = vim.fn.fnamemodify(entry.target.file, ":t")
        table.insert(lines, string.format("%s[%d] %s:%d (%s) → %s:%d (%s)",
            marker, i,
            source_name, entry.source.line, entry.source.word,
            target_name, entry.target.line, entry.target.word))
    end

    vim.notify(table.concat(lines, "\n"), vim.log.levels.INFO)
end

-- Disable mouse mappings
function M.disable()
    if M.config then
        M.config.enable_mouse = false
    end

    -- Clear the autocommand group
    vim.api.nvim_create_augroup("NvimCtagTap", { clear = true })

    -- Clear existing mappings in all buffers
    if M.config then
        local mapping = "<LeftMouse>"

        -- Delete the mapping from all buffers
        for _, buf in ipairs(vim.api.nvim_list_bufs()) do
            if vim.api.nvim_buf_is_valid(buf) then
                pcall(vim.keymap.del, "n", mapping, { buffer = buf })
            end
        end
    end
end

-- Toggle plugin state
function M.toggle()
    if M.config and M.config.enable_mouse then
        M.disable()
        return false  -- Return false to indicate disabled
    else
        M.enable()
        return true   -- Return true to indicate enabled
    end
end

return M