-- Lux language support for Neovim
-- Provides LSP configuration, REPL integration, and utilities

local M = {}

-- Default configuration
M.config = {
  -- Path to lux binary (will search PATH if not set)
  lux_binary = nil,
  -- Enable LSP
  lsp = {
    enabled = true,
    -- Auto-start LSP when opening .lux files
    autostart = true,
    -- Show inline hints (requires neovim 0.10+)
    inlay_hints = false,
  },
  -- Formatting options
  format = {
    -- Format on save
    on_save = false,
  },
  -- REPL options
  repl = {
    -- Position of REPL window: "bottom", "right", "float"
    position = "bottom",
    -- Size of REPL window (percentage)
    size = 30,
  },
  -- Diagnostics options
  diagnostics = {
    -- Show virtual text for diagnostics
    virtual_text = true,
    -- Update diagnostics in insert mode
    update_in_insert = false,
  },
}

-- REPL state
M.repl = {
  bufnr = nil,
  winnr = nil,
  jobid = nil,
}

-- Find lux binary
local function find_lux_binary()
  if M.config.lux_binary then
    return M.config.lux_binary
  end
  -- Try to find in PATH
  local handle = io.popen("which lux 2>/dev/null")
  if handle then
    local result = handle:read("*a"):gsub("%s+", "")
    handle:close()
    if result ~= "" then
      return result
    end
  end
  -- Try common locations
  local common_paths = {
    "./result/bin/lux",
    "./target/release/lux",
    "./target/debug/lux",
    "~/.local/bin/lux",
    "/usr/local/bin/lux",
  }
  for _, path in ipairs(common_paths) do
    local expanded = vim.fn.expand(path)
    if vim.fn.executable(expanded) == 1 then
      return expanded
    end
  end
  return "lux" -- Fallback, hope it's in PATH
end

-- Setup LSP
function M.setup_lsp()
  local lux_binary = find_lux_binary()

  -- Check if lspconfig is available
  local ok, lspconfig = pcall(require, "lspconfig")
  if not ok then
    vim.notify("lspconfig not found. Install nvim-lspconfig for LSP support.", vim.log.levels.WARN)
    return
  end

  local configs = require("lspconfig.configs")

  -- Register lux LSP if not already registered
  if not configs.lux_lsp then
    configs.lux_lsp = {
      default_config = {
        cmd = { lux_binary, "lsp" },
        filetypes = { "lux" },
        root_dir = function(fname)
          return lspconfig.util.find_git_ancestor(fname)
            or lspconfig.util.root_pattern("lux.toml", "package.lux")(fname)
            or vim.fn.getcwd()
        end,
        settings = {},
      },
    }
  end

  -- Setup the LSP
  lspconfig.lux_lsp.setup({
    on_attach = function(client, bufnr)
      -- Enable completion
      vim.bo[bufnr].omnifunc = "v:lua.vim.lsp.omnifunc"

      -- Key mappings
      local opts = { noremap = true, silent = true, buffer = bufnr }
      vim.keymap.set("n", "gd", vim.lsp.buf.definition, opts)
      vim.keymap.set("n", "gD", vim.lsp.buf.declaration, opts)
      vim.keymap.set("n", "K", vim.lsp.buf.hover, opts)
      vim.keymap.set("n", "gi", vim.lsp.buf.implementation, opts)
      vim.keymap.set("n", "<C-k>", vim.lsp.buf.signature_help, opts)
      vim.keymap.set("n", "gr", vim.lsp.buf.references, opts)
      vim.keymap.set("n", "<leader>rn", vim.lsp.buf.rename, opts)
      vim.keymap.set("n", "<leader>ca", vim.lsp.buf.code_action, opts)
      vim.keymap.set("n", "[d", vim.diagnostic.goto_prev, opts)
      vim.keymap.set("n", "]d", vim.diagnostic.goto_next, opts)
      vim.keymap.set("n", "<leader>e", vim.diagnostic.open_float, opts)
      vim.keymap.set("n", "<leader>q", vim.diagnostic.setloclist, opts)

      -- Enable inlay hints if supported
      if M.config.lsp.inlay_hints and vim.lsp.inlay_hint then
        vim.lsp.inlay_hint.enable(true, { bufnr = bufnr })
      end

      -- Format on save if enabled
      if M.config.format.on_save then
        vim.api.nvim_create_autocmd("BufWritePre", {
          buffer = bufnr,
          callback = function()
            vim.lsp.buf.format({ async = false })
          end,
        })
      end
    end,
    capabilities = vim.lsp.protocol.make_client_capabilities(),
  })
end

-- Configure diagnostics display
local function setup_diagnostics()
  vim.diagnostic.config({
    virtual_text = M.config.diagnostics.virtual_text,
    signs = true,
    underline = true,
    update_in_insert = M.config.diagnostics.update_in_insert,
    severity_sort = true,
    float = {
      border = "rounded",
      source = "always",
      header = "",
      prefix = "",
    },
  })

  -- Custom diagnostic signs
  local signs = { Error = " ", Warn = " ", Hint = " ", Info = " " }
  for type, icon in pairs(signs) do
    local hl = "DiagnosticSign" .. type
    vim.fn.sign_define(hl, { text = icon, texthl = hl, numhl = "" })
  end
end

-- Run current file
function M.run_file()
  local file = vim.fn.expand("%:p")
  local lux_binary = find_lux_binary()

  -- Save the file first
  vim.cmd("write")

  -- Run in a terminal split
  vim.cmd("botright split | resize 15")
  vim.fn.termopen(lux_binary .. " " .. vim.fn.shellescape(file), {
    on_exit = function(_, exit_code, _)
      if exit_code == 0 then
        vim.notify("Lux: Run completed successfully", vim.log.levels.INFO)
      end
    end,
  })
  vim.cmd("startinsert")
end

-- Format current file
function M.format_file()
  local file = vim.fn.expand("%:p")
  local lux_binary = find_lux_binary()

  -- Save first
  vim.cmd("write")

  -- Format
  local output = vim.fn.system(lux_binary .. " fmt " .. vim.fn.shellescape(file))
  if vim.v.shell_error ~= 0 then
    vim.notify("Format error: " .. output, vim.log.levels.ERROR)
  else
    vim.cmd("edit!") -- Reload the file
    vim.notify("Formatted", vim.log.levels.INFO)
  end
end

-- Run tests
function M.run_tests()
  local lux_binary = find_lux_binary()
  vim.cmd("botright split | resize 15")
  vim.fn.termopen(lux_binary .. " test")
  vim.cmd("startinsert")
end

-- Start REPL in terminal
function M.start_repl()
  local lux_binary = find_lux_binary()

  -- Close existing REPL if open
  if M.repl.bufnr and vim.api.nvim_buf_is_valid(M.repl.bufnr) then
    vim.api.nvim_buf_delete(M.repl.bufnr, { force = true })
  end

  -- Create REPL window based on config
  local pos = M.config.repl.position
  local size = M.config.repl.size

  if pos == "float" then
    local width = math.floor(vim.o.columns * 0.8)
    local height = math.floor(vim.o.lines * 0.8)
    local row = math.floor((vim.o.lines - height) / 2)
    local col = math.floor((vim.o.columns - width) / 2)

    M.repl.bufnr = vim.api.nvim_create_buf(false, true)
    M.repl.winnr = vim.api.nvim_open_win(M.repl.bufnr, true, {
      relative = "editor",
      width = width,
      height = height,
      row = row,
      col = col,
      style = "minimal",
      border = "rounded",
    })
  elseif pos == "right" then
    vim.cmd("botright vsplit")
    vim.cmd("vertical resize " .. math.floor(vim.o.columns * size / 100))
    M.repl.winnr = vim.api.nvim_get_current_win()
    M.repl.bufnr = vim.api.nvim_get_current_buf()
  else -- bottom
    vim.cmd("botright split")
    vim.cmd("resize " .. math.floor(vim.o.lines * size / 100))
    M.repl.winnr = vim.api.nvim_get_current_win()
    M.repl.bufnr = vim.api.nvim_get_current_buf()
  end

  M.repl.jobid = vim.fn.termopen(lux_binary, {
    on_exit = function()
      M.repl.jobid = nil
      M.repl.bufnr = nil
      M.repl.winnr = nil
    end,
  })
  vim.cmd("startinsert")
end

-- Send text to REPL
function M.send_to_repl(text)
  if not M.repl.jobid then
    vim.notify("No REPL running. Start one with :LuxRepl", vim.log.levels.WARN)
    return
  end

  -- Send the text followed by newline
  vim.fn.chansend(M.repl.jobid, text .. "\n")

  -- Focus the REPL window
  if M.repl.winnr and vim.api.nvim_win_is_valid(M.repl.winnr) then
    vim.api.nvim_set_current_win(M.repl.winnr)
    vim.cmd("startinsert")
  end
end

-- Send current line to REPL
function M.send_line_to_repl()
  local line = vim.api.nvim_get_current_line()
  M.send_to_repl(line)
end

-- Send visual selection to REPL
function M.send_selection_to_repl()
  -- Get visual selection
  local start_pos = vim.fn.getpos("'<")
  local end_pos = vim.fn.getpos("'>")
  local lines = vim.api.nvim_buf_get_lines(0, start_pos[2] - 1, end_pos[2], false)

  if #lines == 0 then
    return
  end

  -- Trim the selection
  if #lines == 1 then
    lines[1] = string.sub(lines[1], start_pos[3], end_pos[3])
  else
    lines[1] = string.sub(lines[1], start_pos[3])
    lines[#lines] = string.sub(lines[#lines], 1, end_pos[3])
  end

  local text = table.concat(lines, "\n")
  M.send_to_repl(text)
end

-- Toggle REPL window
function M.toggle_repl()
  if M.repl.winnr and vim.api.nvim_win_is_valid(M.repl.winnr) then
    vim.api.nvim_win_hide(M.repl.winnr)
    M.repl.winnr = nil
  elseif M.repl.bufnr and vim.api.nvim_buf_is_valid(M.repl.bufnr) then
    -- Reopen existing REPL
    local pos = M.config.repl.position
    local size = M.config.repl.size

    if pos == "right" then
      vim.cmd("botright vsplit")
      vim.cmd("vertical resize " .. math.floor(vim.o.columns * size / 100))
    else
      vim.cmd("botright split")
      vim.cmd("resize " .. math.floor(vim.o.lines * size / 100))
    end

    vim.api.nvim_set_current_buf(M.repl.bufnr)
    M.repl.winnr = vim.api.nvim_get_current_win()
    vim.cmd("startinsert")
  else
    M.start_repl()
  end
end

-- Check current file for errors
function M.check_file()
  local file = vim.fn.expand("%:p")
  local lux_binary = find_lux_binary()

  -- Save first
  vim.cmd("write")

  local output = vim.fn.system(lux_binary .. " check " .. vim.fn.shellescape(file))
  if vim.v.shell_error ~= 0 then
    vim.notify("Type check errors:\n" .. output, vim.log.levels.ERROR)
  else
    vim.notify("No errors found", vim.log.levels.INFO)
  end
end

-- Setup function
function M.setup(opts)
  -- Merge user config
  M.config = vim.tbl_deep_extend("force", M.config, opts or {})

  -- Setup diagnostics
  setup_diagnostics()

  -- Setup LSP if enabled
  if M.config.lsp.enabled and M.config.lsp.autostart then
    vim.api.nvim_create_autocmd("FileType", {
      pattern = "lux",
      callback = function()
        M.setup_lsp()
      end,
      once = true,
    })
  end

  -- Create user commands
  vim.api.nvim_create_user_command("LuxRun", M.run_file, { desc = "Run current Lux file" })
  vim.api.nvim_create_user_command("LuxFormat", M.format_file, { desc = "Format current Lux file" })
  vim.api.nvim_create_user_command("LuxTest", M.run_tests, { desc = "Run Lux tests" })
  vim.api.nvim_create_user_command("LuxRepl", M.start_repl, { desc = "Start Lux REPL" })
  vim.api.nvim_create_user_command("LuxReplToggle", M.toggle_repl, { desc = "Toggle Lux REPL" })
  vim.api.nvim_create_user_command("LuxCheck", M.check_file, { desc = "Type check current Lux file" })
  vim.api.nvim_create_user_command("LuxSendLine", M.send_line_to_repl, { desc = "Send line to REPL" })

  -- Create command with range for sending selection
  vim.api.nvim_create_user_command("LuxSend", function(opts)
    if opts.range == 2 then
      M.send_selection_to_repl()
    else
      M.send_line_to_repl()
    end
  end, { range = true, desc = "Send to REPL" })

  -- Setup keymaps for Lux files
  vim.api.nvim_create_autocmd("FileType", {
    pattern = "lux",
    callback = function(ev)
      local buf_opts = { noremap = true, silent = true, buffer = ev.buf }

      -- Run/format/test commands
      vim.keymap.set("n", "<localleader>r", M.run_file, buf_opts)
      vim.keymap.set("n", "<localleader>f", M.format_file, buf_opts)
      vim.keymap.set("n", "<localleader>t", M.run_tests, buf_opts)
      vim.keymap.set("n", "<localleader>c", M.check_file, buf_opts)

      -- REPL commands
      vim.keymap.set("n", "<localleader>R", M.toggle_repl, buf_opts)
      vim.keymap.set("n", "<localleader>l", M.send_line_to_repl, buf_opts)
      vim.keymap.set("v", "<localleader>s", M.send_selection_to_repl, buf_opts)
    end,
  })
end

return M
