-- Load persistent configuration from .workspace.lua
local ws_file = "./.nvim.workspace.lua"
local loaded, workspace = pcall(dofile, ws_file)
if not loaded then
  workspace = { args = {}, build_type = "debug", binary = "blog-server" }
end

local build_folder
local bin_target
local bin_name

local function updateBuildEnv()
  build_folder = './target/' .. workspace.build_type
  bin_target = workspace.binary
  bin_name = build_folder .. '/' .. bin_target

  -- The run (F6) arguments
  vim.opt.makeprg = "cargo build"
  vim.g.cargo_makeprg_params = 'build'
  if workspace.build_type == "release" then
    vim.opt.makeprg = vim.opt.makeprg .. " --release"
    vim.g.cargo_makeprg_params = vim.g.cargo_makeprg_params .. " --release"
  end

  -- Rust compiler error format
  vim.opt.errorformat = {
    -- Main error/warning line with file:line:col format
    '%E%>error%m,' ..        -- Start of error block
    '%W%>warning: %m,' ..    -- Start of warning block
    '%-G%>help: %m,' ..      -- Ignore help lines
    '%-G%>note: %m,' ..      -- Ignore note lines
    '%C%>  --> %f:%l:%c,' .. -- Continuation: file location
    '%Z%>%p^%m,' ..          -- End: column pointer (^^^)
    '%C%>%s%#|%.%#,' ..      -- Continuation: context lines with |
    '%C%>%s%#%m,' ..         -- Continuation: other context
    '%-G%.%#'                -- Ignore everything else
  }
end

updateBuildEnv()

-- Prevent Vim's built-in rust ftplugin from loading the cargo compiler
vim.api.nvim_create_autocmd("FileType", {
  pattern = "rust",
  callback = function()
    vim.b.current_compiler = 'custom'
  end,
})

local function writeWorkspace()
  -- A very minimal serializer for workspace configuration
  local s = { l = "", ls = {}, i = "" }
  local function w(v) s.l = s.l .. v end
  local function nl()
    s.ls[#s.ls + 1] = s.l; s.l = s.i;
  end
  local function wv(v)
    local t = type(v)
    if t == 'table' then
      w('{'); local pi = s.i; s.i = s.i .. "  "
      for k1, v1 in pairs(v) do
        nl(); w('['); wv(k1); w('] = '); wv(v1); w(',')
      end
      s.i = pi; nl(); w('}');
    elseif t == 'number' then
      w(tostring(v))
    elseif t == 'string' then
      w('"' .. v .. '"')
    else
      w(tostring(v))
    end
  end

  -- Write the workspace file
  w("return "); wv(workspace); nl()
  vim.fn.writefile(s.ls, ws_file)
end

-- nvim-dap configuration
local dap_ok, dap = pcall(require, "dap")
local dap_def_cfg
local dap_configs

-- Update args for both run and debug configs
local function updateArgs(args)
  workspace.args = args
  if dap_configs ~= nil then dap_configs[1].args = args end
  writeWorkspace()
end

-- Find terminal tab or create new one, then run command
local function TermRun(cmd)
  local found = false
  for i = 1, vim.fn.tabpagenr('$') do
    vim.cmd('tabnext ' .. i)
    if vim.bo.buftype == 'terminal' then
      found = true
      break
    end
  end
  if not found then vim.cmd('tabnew | terminal') end
  vim.fn.chansend(vim.b.terminal_job_id, '' .. cmd .. '\n')
  vim.fn.feedkeys("G", "n")
end

-- The Configure command
vim.api.nvim_create_user_command("Configure", function(a)
  local args = {}
  local bt = "debug"
  if #a.args > 0 then bt = a.args end
  workspace.build_type = bt
  updateBuildEnv()
  writeWorkspace()
end, { nargs = '?', desc = "Update run/debug arguments" })

vim.api.nvim_create_user_command("Args", function(a) updateArgs(a.fargs) end,
  { nargs = '*', desc = "Update run/debug arguments" })

if dap_ok then
  local rust_path = vim.fn.system("rustc --print sysroot") -- We need to query this to get the sysroot
  rust_path = string.sub(rust_path, 1, #rust_path - 1)     -- trim trailing newline
  local lldb_init = {
    "command script import " .. rust_path .. "/lib/rustlib/etc/lldb_lookup.py",
    "command source " .. rust_path .. "/lib/rustlib/etc/lldb_commands",
  }
  dap_configs = {
    {
      name = 'test all',
      type = 'codelldb',
      request = 'launch',
      program = bin_name,
      args = workspace.args,
      cwd = '${workspaceFolder}',
      stopOnEntry = false,
      initCommands = lldb_init
    }
  }
  dap_def_cfg = dap_configs[1]
  dap.providers.configs["project"] = function()
    return dap_configs
  end

  -- DebugArgs to set debugger arguments and run immediately
  vim.api.nvim_create_user_command("DebugArgs", function(a)
    updateArgs(a.fargs)
    dap.run(dap_configs[1])
  end, { nargs = '*', desc = "Starts debugging with specified arguments" })
end

if MakeAnd then
  local r = function()
    MakeAnd(function()
      TermRun(bin_name .. " " .. table.concat(workspace.args, " "))
    end)
  end
  -- RunArgs sets the run arguments that F6 uses and reruns immediately
  vim.api.nvim_create_user_command("RunArgs", function(a)
    updateArgs(a.fargs)
    r()
  end, { nargs = '*', desc = "Starts debugging with specified arguments" })

  -- F6 to run the application
  vim.keymap.set('n', '<F6>', r)

  if dap_ok then
    -- Shift-F5 to launch default config
    vim.keymap.set('n', '<F17>', function()
      MakeAnd(function()
        dap.run(dap_def_cfg)
      end)
    end)
  end
end
