-- smarana.nvim — Utility functions
-- Notebook detection, sma CLI interface, slug generation

local M = {}

--- Cached notebook root from global config (resolved once)
local _cached_root = nil

--- Find the smarana notebook root by walking up from path.
--- Looks for a directory containing both smarana.typ and .smarana/
---@param path string absolute path to a file
---@return string|nil root absolute path to notebook root, or nil
function M.notebook_root(path)
  local dir = vim.fn.fnamemodify(path, ":p:h")
  while dir and dir ~= "/" do
    if vim.fn.filereadable(dir .. "/smarana.typ") == 1
      and vim.fn.isdirectory(dir .. "/.smarana") == 1 then
      return dir
    end
    local parent = vim.fn.fnamemodify(dir, ":h")
    if parent == dir then break end
    dir = parent
  end
  return nil
end

--- Resolve the notebook root globally.
--- First tries the current file context (walking up), then falls back
--- to the global config at XDG_CONFIG_HOME/smarana/config.toml.
---@return string|nil root absolute path to notebook root
function M.resolve_root()
  -- Try context-local first
  local filepath = vim.fn.expand("%:p")
  if filepath and filepath ~= "" then
    local local_root = M.notebook_root(filepath)
    if local_root then return local_root end
  end

  -- Use cached global root
  if _cached_root then return _cached_root end

  -- Read from global config
  local config_home = vim.env.XDG_CONFIG_HOME or (vim.env.HOME .. "/.config")
  local config_path = config_home .. "/smarana/config.toml"
  if vim.fn.filereadable(config_path) ~= 1 then return nil end

  local lines = vim.fn.readfile(config_path)
  for _, line in ipairs(lines) do
    local notebook = line:match('^%s*notebook%s*=%s*"(.-)"')
    if notebook then
      -- Expand $HOME
      notebook = notebook:gsub("%$HOME", vim.env.HOME or "")
      if vim.fn.isdirectory(notebook) == 1 then
        _cached_root = notebook
        return notebook
      end
    end
  end

  return nil
end

--- Get all notes via `sma --json`.
---@return table[] notes array of note objects {filename, title, date, time, tags, ...}
function M.get_notes()
  local output = vim.fn.system("sma --json")
  if vim.v.shell_error ~= 0 then
    vim.notify("smarana: failed to get notes", vim.log.levels.ERROR)
    return {}
  end
  local ok, notes = pcall(vim.json.decode, output)
  if not ok or not notes then
    vim.notify("smarana: failed to parse notes JSON", vim.log.levels.ERROR)
    return {}
  end
  return notes
end

--- Generate a label slug from a title (matches Typst make-slug in library.typ).
--- "Closures in Rust" → "Closures-in-Rust"
---@param title string
---@return string slug
function M.make_slug(title)
  local slug = title:gsub("[^a-zA-Z0-9]+", "-"):gsub("^-+", ""):gsub("-+$", "")
  return slug == "" and "untitled" or slug
end

--- Create a note via `sma --no-edit -n "title"` and return its file path.
---@param title string note title
---@param opts? { type?: string, content?: string }
---@return string|nil path absolute path to the created/existing note
function M.create_note(title, opts)
  opts = opts or {}
  local root = M.resolve_root()
  if not root then
    vim.notify("smarana: notebook not found", vim.log.levels.ERROR)
    return nil
  end

  local cmd = "sma --no-edit -n " .. vim.fn.shellescape(title)
  if opts.type then
    cmd = cmd .. " -T " .. vim.fn.shellescape(opts.type)
  end
  if opts.tags and opts.tags ~= "" then
    cmd = cmd .. " --tags"
    local i = 1
    while i <= #opts.tags do
      while opts.tags:sub(i,i):match("%s") do i = i + 1 end
      if i > #opts.tags then break end
      if opts.tags:sub(i,i) == '"' then
        local j = opts.tags:find('"', i + 1)
        if j then
          cmd = cmd .. " " .. vim.fn.shellescape(opts.tags:sub(i+1, j-1))
          i = j + 1
        else
          cmd = cmd .. " " .. vim.fn.shellescape(opts.tags:sub(i+1))
          break
        end
      else
        local j = opts.tags:find("%s", i)
        if j then
          cmd = cmd .. " " .. vim.fn.shellescape(opts.tags:sub(i, j-1))
          i = j
        else
          cmd = cmd .. " " .. vim.fn.shellescape(opts.tags:sub(i))
          break
        end
      end
    end
  end
  if opts.content then
    cmd = cmd .. " -i"
  end

  local output
  if opts.content then
    output = vim.fn.system(cmd, opts.content)
  else
    output = vim.fn.system(cmd)
  end

  if vim.v.shell_error ~= 0 then
    vim.notify("smarana: " .. output, vim.log.levels.ERROR)
    return nil
  end

  -- The output is "Created note: /absolute/path.typ\n"
  local matched_path = output:match("Created note: (.+%.typ)")
  if matched_path then
     return vim.trim(matched_path)
  end

  -- Fallback to old behavior
  local notes = M.get_notes()
  for _, note in ipairs(notes) do
    if note.title == title then
      return root .. "/" .. note.filename
    end
  end

  return nil
end

--- Sync the notebook database.
function M.sync()
  local output = vim.fn.system("sma --sync")
  if vim.v.shell_error ~= 0 then
    vim.notify("smarana: sync failed: " .. output, vim.log.levels.ERROR)
  end
end

--- Get the current visual selection text.
---@return string text the selected text
function M.get_visual_selection()
  vim.cmd('noau normal! "vy')
  local text = vim.fn.getreg("v")
  vim.fn.setreg("v", "")
  return text
end

--- Replace the last visual selection with replacement text.
---@param replacement string text to replace the selection with
function M.replace_visual_selection(replacement)
  vim.fn.setreg("v", replacement)
  vim.cmd('noau normal! gv"vp')
end

return M
