local utils = require "nvim-treesitter-playground.utils"
local highlighter = require "vim.treesitter.highlighter"
local ts_utils = require "nvim-treesitter.ts_utils"
local parsers = require "nvim-treesitter.parsers"

local M = {}

function M.get_treesitter_hl()
  local bufnr = vim.api.nvim_get_current_buf()
  local row, col = unpack(vim.api.nvim_win_get_cursor(0))
  row = row - 1

  local results = utils.get_hl_groups_at_position(bufnr, row, col)
  local highlights = {}
  for _, hl in pairs(results) do
    local line = "* **@" .. hl.capture .. "**"
    if hl.specific then
      line = line .. " -> **" .. hl.specific .. "**"
    end
    if hl.general then
      line = line .. " -> **" .. hl.general .. "**"
    end
    if hl.priority then
      line = line .. "(" .. hl.priority .. ")"
    end
    table.insert(highlights, line)
  end
  return highlights
end

function M.get_syntax_hl()
  local line = vim.fn.line "."
  local col = vim.fn.col "."
  local matches = {}
  for _, i1 in ipairs(vim.fn.synstack(line, col)) do
    local i2 = vim.fn.synIDtrans(i1)
    local n1 = vim.fn.synIDattr(i1, "name")
    local n2 = vim.fn.synIDattr(i2, "name")
    table.insert(matches, "* " .. n1 .. " -> **" .. n2 .. "**")
  end
  return matches
end

function M.show_hl_captures()
  local buf = vim.api.nvim_get_current_buf()
  local lines = {}

  local function show_matches(matches)
    if #matches == 0 then
      table.insert(lines, "* No highlight groups found")
    end
    for _, line in ipairs(matches) do
      table.insert(lines, line)
    end
    table.insert(lines, "")
  end

  if highlighter.active[buf] then
    table.insert(lines, "# Treesitter")
    local matches = M.get_treesitter_hl()
    show_matches(matches)
  end

  if vim.b.current_syntax then
    table.insert(lines, "# Syntax")
    local matches = M.get_syntax_hl()
    show_matches(matches)
  end

  vim.lsp.util.open_floating_preview(lines, "markdown", { border = "single", pad_left = 4, pad_right = 4 })
end

-- Show Node at Cursor
---@param opts? table with optional fields
---             - full_path: (boolean, default false) show full path to current node
---             - show_range: (boolean, default true) show range of current node
---             - include_anonymous: (boolean, default false) include anonymous node
---             - highlight_node: (boolean, default true) highlight the current node
---             - hl_group: (string, default "TSPlaygroundFocus") name of group
--- @return number|nil bufnr number
function M.show_ts_node(opts)
  opts = vim.tbl_deep_extend("keep", opts or {}, {
    full_path = false,
    show_range = true,
    include_anonymous = false,
    highlight_node = true,
    hl_group = "TSPlaygroundFocus",
  })

  if not parsers.has_parser() then
    return
  end

  -- Get Full Path to node
  -- @param node
  -- @param array?
  -- @return string
  local function get_full_path(node, array)
    local parent = node:parent()
    if parent == nil then
      if array == nil then
        return node:type()
      end
      local reverse = vim.tbl_map(function(index)
        return array[#array + 1 - index]:type()
      end, vim.tbl_keys(array))
      return table.concat(reverse, " -> ")
    end
    return get_full_path(parent, vim.list_extend(array or {}, { node }))
  end

  local cursor = vim.api.nvim_win_get_cursor(0)
  local line = cursor[1] - 1
  local col = cursor[2]

  local bufnr = 0
  local root_lang_tree = parsers.get_parser(bufnr)
  local lang_tree = root_lang_tree:language_for_range { line, col, line, col }

  local lines = { "# Treesitter" }
  local node_under_cursor

  for _, tree in ipairs(lang_tree:trees()) do
    local root = tree:root()
    if root and ts_utils.is_in_node_range(root, line, col) then
      local node = root:named_descendant_for_range(line, col, line, col)
      local path = opts.full_path and get_full_path(node) or node:type()

      node_under_cursor = node

      vim.list_extend(lines, {
        "* Parser: " .. lang_tree:lang(),
        string.format("* %s: ", opts.full_path and "Node path" or "Node") .. path,
      })

      if opts.include_anonymous then
        local anonymous_node = root:descendant_for_range(line, col, line, col)
        vim.list_extend(lines, {
          " - Anonymous: " .. anonymous_node:type(),
        })
      end

      if opts.show_range then
        local srow, scol, erow, ecol = ts_utils.get_vim_range({ node:range() }, bufnr)

        vim.list_extend(lines, {
          "* Range: ",
          "  - Start row: " .. srow,
          "  - End row: " .. erow,
          "  - Start col: " .. scol,
          "  - End col: " .. ecol,
        })
      end
    end
  end

  if not node_under_cursor then
    lines[#lines + 1] = "* Node not found"
  end

  if opts.highlight_node and node_under_cursor then
    local ns = vim.api.nvim_create_namespace "nvim-treesitter-current-node"

    ts_utils.highlight_node(node_under_cursor, bufnr, ns, opts.hl_group)
    vim.api.nvim_create_autocmd("CursorMoved", {
      group = vim.api.nvim_create_augroup("TSNodeUnderCursor", {}),
      buffer = bufnr,
      callback = function()
        require("nvim-treesitter-playground.internal").clear_highlights(bufnr, ns)
      end,
      desc = "TSPlayground: clear highlights",
    })
  end

  return vim.lsp.util.open_floating_preview(lines, "markdown", { border = "single", pad_left = 4, pad_right = 4 })
end

return M
