-- smarana.nvim — A neovim plugin for the smarana note-taking system
-- Integrates with the `sma` CLI and typst-preview.nvim

local M = {}
local util = require("smarana.util")
local preview = require("smarana.preview")

local note_types = { "fleeting", "capture", "atomic" }

--- Default configuration
M.config = {
  -- Auto-start preview when opening a smarana notebook note
  preview = true,
  -- Default note type: "fleeting", "capture", "atomic", or "ask"
  -- When "ask", SmaNew will prompt for the type via vim.ui.select.
  -- When set to a specific type, SmaNew skips the prompt.
  default_type = "ask",
}

--- Set up the plugin. Call this from your nvim config.
---@param opts? table user configuration overrides
function M.setup(opts)
  M.config = vim.tbl_deep_extend("force", M.config, opts or {})

  -- ── User commands (globally available) ──

  vim.api.nvim_create_user_command("SmaNew", function(cmd)
    local title = cmd.args ~= "" and cmd.args or nil
    M.new_note(title)
  end, { nargs = "?", desc = "Create a new smarana note" })

  vim.api.nvim_create_user_command("SmaNewFleeting", function(cmd)
    local title = cmd.args ~= "" and cmd.args or nil
    M.new_note(title, { type = "fleeting" })
  end, { nargs = "?", desc = "Create a new fleeting note" })

  vim.api.nvim_create_user_command("SmaNewCapture", function(cmd)
    local title = cmd.args ~= "" and cmd.args or nil
    M.new_note(title, { type = "capture" })
  end, { nargs = "?", desc = "Create a new capture note" })

  vim.api.nvim_create_user_command("SmaNewAtomic", function(cmd)
    local title = cmd.args ~= "" and cmd.args or nil
    M.new_note(title, { type = "atomic" })
  end, { nargs = "?", desc = "Create a new atomic note" })

  vim.api.nvim_create_user_command("SmaNotes", function()
    M.pick_note()
  end, { desc = "Search and open a smarana note" })

  vim.api.nvim_create_user_command("SmaInsertLink", function()
    M.insert_link()
  end, { desc = "Pick a note and insert a @ref link" })

  vim.api.nvim_create_user_command("SmaSync", function()
    util.sync()
  end, { desc = "Sync smarana notebook database" })

  vim.api.nvim_create_user_command("SmaPreview", function()
    preview.toggle()
  end, { desc = "Toggle smarana preview" })

  vim.api.nvim_create_user_command("SmaNewFromTitle", function()
    M.new_from_title()
  end, { range = true, desc = "Create note using selection as title" })

  vim.api.nvim_create_user_command("SmaNewFromContent", function()
    M.new_from_content()
  end, { range = true, desc = "Create note using selection as content" })

  vim.api.nvim_create_user_command("SmaInsertLinkAtSelection", function()
    M.insert_link_at_selection()
  end, { range = true, desc = "Pick a note and wrap selection in link" })

  -- ── Autocmds ──

  local augroup = vim.api.nvim_create_augroup("smarana", { clear = true })

  vim.api.nvim_create_autocmd("FileType", {
    pattern = "typst",
    group = augroup,
    callback = function()
      local filepath = vim.fn.expand("%:p")
      local root = util.notebook_root(filepath)
      if not root then return end

      -- Set working directory to notebook root
      vim.api.nvim_set_current_dir(root)

      -- Auto-start preview
      if M.config.preview then
        vim.schedule(function()
          preview.auto_start(root)
        end)
      end
    end,
  })

  vim.api.nvim_create_autocmd("BufWritePost", {
    pattern = "*.typ",
    group = augroup,
    callback = function()
      local filepath = vim.fn.expand("%:p")
      if util.notebook_root(filepath) then
        util.sync()
      end
    end,
  })
end

--- Restart any attached LSP clients to force a project re-index.
--- Uses native Neovim 0.11+ command.
local function restart_lsp()
  pcall(vim.cmd, "lsp restart")
end

--- Pick a note type via vim.ui.select, then call the callback with it.
---@param callback fun(note_type: string)
local function pick_type(callback)
  vim.ui.select(note_types, {
    prompt = "Note type",
    format_item = function(item)
      return item:sub(1, 1):upper() .. item:sub(2)
    end,
  }, function(choice)
    if not choice then return end
    callback(choice)
  end)
end

--- Create a new note. Prompts for type (unless configured) and title.
---@param title? string optional note title
---@param opts? { type?: string }
function M.new_note(title, opts)
  opts = opts or {}

  -- Resolve note type
  local resolved_type = opts.type
  if not resolved_type then
    local default = M.config.default_type
    if default == "ask" then
      -- Prompt for type first, then continue
      pick_type(function(chosen)
        M.new_note(title, { type = chosen })
      end)
      return
    else
      resolved_type = default
    end
  end

  -- Prompt for title if not given
  if not title then
    vim.ui.input({ prompt = "Note title: " }, function(input)
      if not input or input == "" then return end
      M.new_note(input, { type = resolved_type, tags = opts.tags })
    end)
    return
  end

  if not opts.tags and resolved_type ~= "fleeting" then
    vim.ui.input({ prompt = "Tags (space separated): " }, function(tags_input)
      opts.tags = tags_input or ""
      -- Recall new_note to finish execution with all parameters now satisfied
      M.new_note(title, opts)
    end)
    return
  end

  -- Ensure cwd is the notebook root
  local root = util.resolve_root()
  if root then vim.api.nvim_set_current_dir(root) end

  local path = util.create_note(title, { type = resolved_type, tags = opts.tags })
  if path then
    vim.cmd.edit(path)
    vim.cmd("normal! G")
    vim.notify("smarana: " .. title, vim.log.levels.INFO)
  end
end

--- Pick a note from the notebook and open it. Uses vim.ui.select.
function M.pick_note()
  local notes = util.get_notes()
  if #notes == 0 then
    vim.notify("smarana: no notes found", vim.log.levels.WARN)
    return
  end

  vim.ui.select(notes, {
    prompt = "Open note",
    format_item = function(note)
      local parts = {}
      local ntype = note.note_type or note.type or ""
      if ntype ~= "" then
        table.insert(parts, "[" .. ntype:upper():sub(1, 1) .. "]")
      end
      if note.date then table.insert(parts, note.date) end
      table.insert(parts, note.title or note.filename)
      if type(note.tags) == "table" and #note.tags > 0 then
        table.insert(parts, "(" .. table.concat(note.tags, ", ") .. ")")
      end
      return table.concat(parts, "  ")
    end,
  }, function(note)
    if not note then return end
    local root = util.resolve_root()
    if root then
      vim.api.nvim_set_current_dir(root)
      vim.cmd.edit(root .. "/" .. note.filename)
      vim.cmd("normal! G")
    end
  end)
end

--- Pick a note and insert an @ref link at the cursor.
function M.insert_link()
  local notes = util.get_notes()
  if #notes == 0 then
    vim.notify("smarana: no notes found", vim.log.levels.WARN)
    return
  end

  vim.ui.select(notes, {
    prompt = "Insert link to",
    format_item = function(note)
      return (note.date or "") .. "  " .. (note.title or note.filename)
    end,
  }, function(note)
    if not note then return end
    local slug = util.make_slug(note.title or note.filename:gsub("%.typ$", ""))
    local ref = "@" .. slug
    -- Insert at cursor position
    local row, col = unpack(vim.api.nvim_win_get_cursor(0))
    local line = vim.api.nvim_buf_get_lines(0, row - 1, row, false)[1] or ""
    local new_line = line:sub(1, col) .. ref .. line:sub(col + 1)
    vim.api.nvim_buf_set_lines(0, row - 1, row, false, { new_line })
    vim.api.nvim_win_set_cursor(0, { row, col + #ref })
  end)
end

--- (Visual mode) Create a new note using the selected text as the title.
--- Replaces the selection with an @ref link to the new note.
function M.new_from_title()
  local title = util.get_visual_selection()
  title = vim.trim(title)
  if title == "" then
    vim.notify("smarana: empty selection", vim.log.levels.WARN)
    return
  end

  local function create(note_type)
    local function finish_create(tags_input)
      local path = util.create_note(title, { type = note_type, tags = tags_input })
      if path then
        local slug = util.make_slug(title)
        util.replace_visual_selection("@" .. slug)
        vim.notify("smarana: created " .. title, vim.log.levels.INFO)
        -- Open the newly created note and jump to end
        vim.cmd.edit(path)
        vim.cmd("normal! G")
        -- Restart LSP to ensure new label is indexed immediately
        restart_lsp()
      end
    end

    if note_type == "fleeting" then
      finish_create("")
    else
      vim.ui.input({ prompt = "Tags (space separated): " }, function(tags_input)
        finish_create(tags_input or "")
      end)
    end
  end

  local default = M.config.default_type
  if default == "ask" then
    pick_type(create)
  else
    create(default)
  end
end

--- (Visual mode) Create a new note using the selection as body content.
--- Prompts for a title. Replaces the selection with an @ref link.
function M.new_from_content()
  local content = util.get_visual_selection()
  content = vim.trim(content)
  if content == "" then
    vim.notify("smarana: empty selection", vim.log.levels.WARN)
    return
  end

  local function create(note_type)
    vim.ui.input({ prompt = "Note title: " }, function(input_title)
      if not input_title or input_title == "" then return end

      local function finish_create(tags_input)
        local path = util.create_note(input_title, { content = content, type = note_type, tags = tags_input })
        if path then
          local slug = util.make_slug(input_title)
          util.replace_visual_selection("@" .. slug)
          vim.notify("smarana: created " .. input_title, vim.log.levels.INFO)
          -- Open the newly created note and jump to end
          vim.cmd.edit(path)
          vim.cmd("normal! G")
          -- Restart LSP to ensure new label is indexed immediately
          restart_lsp()
        end
      end

      if note_type == "fleeting" then
        finish_create("")
      else
        vim.ui.input({ prompt = "Tags (space separated): " }, function(tags_input)
          finish_create(tags_input or "")
        end)
      end
    end)
  end

  local default = M.config.default_type
  if default == "ask" then
    pick_type(create)
  else
    create(default)
  end
end

--- (Visual mode) Pick a note and wrap the selection as link text.
--- Replaces selection with `@Slug[selected text]`.
function M.insert_link_at_selection()
  local selected = util.get_visual_selection()
  selected = vim.trim(selected)
  if selected == "" then
    vim.notify("smarana: empty selection", vim.log.levels.WARN)
    return
  end

  local notes = util.get_notes()
  if #notes == 0 then
    vim.notify("smarana: no notes found", vim.log.levels.WARN)
    return
  end

  vim.ui.select(notes, {
    prompt = "Link selection to",
    format_item = function(note)
      return (note.date or "") .. "  " .. (note.title or note.filename)
    end,
  }, function(note)
    if not note then return end
    local slug = util.make_slug(note.title or note.filename:gsub("%.typ$", ""))
    util.replace_visual_selection("@" .. slug .. "[" .. selected .. "]")
  end)
end

return M
