-- This module is responsible for CRUD operations for the draft notes in the discussion tree.
-- That includes things like editing existing draft notes in the tree, and
-- and deleting them. Normal notes and comments are managed separately,
-- under lua/gitlab/actions/discussions/init.lua
local common = require("gitlab.actions.common")
local discussion_tree = require("gitlab.actions.discussions.tree")
local git = require("gitlab.git")
local job = require("gitlab.job")
local NuiTree = require("nui.tree")
local List = require("gitlab.utils.list")
local u = require("gitlab.utils")
local state = require("gitlab.state")

local M = {}

---Re-fetches all draft notes (and non-draft notes) and re-renders the relevant views
---@param unlinked boolean
---@param all boolean|nil
M.rebuild_view = function(unlinked, all)
  M.load_draft_notes(function()
    local discussions = require("gitlab.actions.discussions")
    discussions.rebuild_view(unlinked, all)
  end)
end

---Makes API call to get the discussion data, stores it in the state, and calls the callback
---@param callback function|nil
M.load_draft_notes = function(callback)
  state.discussion_tree.last_updated = nil
  state.load_new_state("draft_notes", function()
    if callback ~= nil then
      callback()
    end
  end)
end

---Will actually send the edits to Gitlab and refresh the draft_notes tree
---@param note_id integer
---@param unlinked boolean
---@return function
M.confirm_edit_draft_note = function(note_id, unlinked)
  return function(text)
    local all_notes = List.new(state.DRAFT_NOTES)
    local the_note = all_notes:find(function(note)
      return note.id == note_id
    end)
    local body = { note = text, position = the_note.position }
    job.run_job(string.format("/mr/draft_notes/%d", note_id), "PATCH", body, function(data)
      u.notify(data.message, vim.log.levels.INFO)
      M.rebuild_view(unlinked)
    end)
  end
end

---This function will actually send the deletion to Gitlab when you make a selection, and re-render the tree
---@param note_id integer
---@param unlinked boolean
M.confirm_delete_draft_note = function(note_id, unlinked)
  job.run_job(string.format("/mr/draft_notes/%d", note_id), "DELETE", nil, function(data)
    u.notify(data.message, vim.log.levels.INFO)
    M.rebuild_view(unlinked)
  end)
end

-- This function will trigger a popup prompting you to publish the current draft comment
M.publish_draft = function(tree)
  vim.ui.select({ "Confirm", "Cancel" }, {
    prompt = "Publish current draft comment?",
  }, function(choice)
    if choice == "Confirm" then
      M.confirm_publish_draft(tree)
    end
  end)
end

-- This function will trigger a popup prompting you to publish all draft notes
M.publish_all_drafts = function()
  vim.ui.select({ "Confirm", "Cancel" }, {
    prompt = "Publish all drafts?",
  }, function(choice)
    if choice == "Confirm" then
      M.confirm_publish_all_drafts()
    end
  end)
end

---Publishes all draft notes and comments. Re-renders all discussion views.
M.confirm_publish_all_drafts = function()
  if not git.check_current_branch_up_to_date_on_remote(vim.log.levels.ERROR) then
    return
  end
  local body = { publish_all = true }
  job.run_job("/mr/draft_notes/publish", "POST", body, function(data)
    u.notify(data.message, vim.log.levels.INFO)
    state.DRAFT_NOTES = {}
    require("gitlab.actions.discussions").rebuild_view(false, true)
  end, function()
    require("gitlab.actions.discussions").rebuild_view(false, true)
    u.notify(
      "Draft(s) may have been published despite the error. Check the discussion tree. Try publishing drafts individually.",
      vim.log.levels.WARN
    )
  end)
end

---Publishes the current draft note that is being hovered over in the tree,
---and then makes an API call to refresh the relevant data for that tree
---and re-render it.
---@param tree NuiTree
M.confirm_publish_draft = function(tree)
  if not git.check_current_branch_up_to_date_on_remote(vim.log.levels.ERROR) then
    return
  end
  local current_node = tree:get_node()
  local note_node = common.get_note_node(tree, current_node)
  local root_node = common.get_root_node(tree, current_node)

  if note_node == nil or root_node == nil then
    u.notify("Could not get note or root node", vim.log.levels.ERROR)
    return
  end

  ---@type integer
  local note_id = note_node.is_root and root_node.id or note_node.id
  local body = { note = note_id }
  local unlinked = tree.bufnr == require("gitlab.actions.discussions").unlinked_bufnr
  job.run_job("/mr/draft_notes/publish", "POST", body, function(data)
    u.notify(data.message, vim.log.levels.INFO)
    M.rebuild_view(unlinked)
  end, function()
    M.rebuild_view(unlinked)
    u.notify("Draft may have been published despite the error. Check the discussion tree.", vim.log.levels.WARN)
  end)
end

--- Helper functions
---Tells whether a draft note was left on a particular diff or is an unlinked note
---@param note DraftNote
M.has_position = function(note)
  return note.position.new_path ~= nil or note.position.old_path ~= nil
end

---Builds a note for the discussion tree for draft notes that are roots
---of their own discussions, e.g. not replies
---@param note DraftNote
---@return NuiTree.Node
M.build_root_draft_note = function(note)
  local _, root_text, root_text_nodes = discussion_tree.build_note(note)
  return NuiTree.Node({
    range = (type(note.position) == "table" and note.position.line_range or nil),
    text = root_text,
    type = "note",
    is_root = true,
    is_draft = true,
    id = note.id,
    root_note_id = note.id,
    file_name = (type(note.position) == "table" and note.position.new_path or nil),
    old_file_name = (type(note.position) == "table" and note.position.old_path or nil),
    new_line = (type(note.position) == "table" and note.position.new_line or nil),
    old_line = (type(note.position) == "table" and note.position.old_line or nil),
    resolvable = false,
    resolved = false,
    url = state.INFO.web_url .. "#note_" .. note.id,
  }, root_text_nodes)
end

---Returns a list of nodes to add to the discussion tree. Can filter and return only unlinked (note) nodes.
---@param unlinked boolean
---@return NuiTree.Node[]
M.add_draft_notes_to_table = function(unlinked)
  local draft_notes = List.new(state.DRAFT_NOTES)
  local draft_note_nodes = draft_notes
    ---@param note DraftNote
    :filter(function(note)
      if unlinked then
        return not M.has_position(note)
      end
      return M.has_position(note)
    end)
    :filter(function(note)
      return note.discussion_id == "" -- Do not include draft replies
    end)
    :map(M.build_root_draft_note)

  return draft_note_nodes
end

return M
