local Util = require("lazy.util")
local Git = require("lazy.manage.git")
local Lock = require("lazy.manage.lock")
local Config = require("lazy.core.config")

---@type table<string, LazyTaskDef>
local M = {}

M.log = {
  ---@param opts {updated?:boolean, check?: boolean}
  skip = function(plugin, opts)
    if opts.updated and not (plugin._.updated and plugin._.updated.from ~= plugin._.updated.to) then
      return true
    end
    return not Util.file_exists(plugin.dir .. "/.git")
  end,
  ---@param opts {args?: string[], updated?:boolean, check?:boolean}
  run = function(self, opts)
    local args = {
      "log",
      "--pretty=format:%h %s (%cr)",
      "--abbrev-commit",
      "--decorate",
      "--date=short",
      "--color=never",
    }

    if opts.updated then
      table.insert(args, self.plugin._.updated.from .. ".." .. (self.plugin._.updated.to or "HEAD"))
    elseif opts.check then
      local info = assert(Git.info(self.plugin.dir))
      local target = assert(Git.get_target(self.plugin))
      assert(target.commit, self.plugin.name .. " " .. target.branch)
      self.plugin._.has_updates = target.commit ~= info.commit
      table.insert(args, info.commit .. ".." .. target.commit)
    else
      vim.list_extend(args, opts.args or Config.options.git.log)
    end

    self:spawn("git", {
      args = args,
      cwd = self.plugin.dir,
    })
  end,
}

M.clone = {
  skip = function(plugin)
    return plugin._.installed or plugin._.is_local
  end,
  run = function(self)
    local args = {
      "clone",
      self.plugin.url,
      "--filter=blob:none",
      "--recurse-submodules",
      "--single-branch",
      "--shallow-submodules",
      "--no-checkout",
      "--progress",
    }

    if self.plugin.branch then
      vim.list_extend(args, { "-b", self.plugin.branch })
    end

    table.insert(args, self.plugin.dir)
    self:spawn("git", {
      args = args,
      on_exit = function(ok)
        if ok then
          self.plugin._.cloned = true
          self.plugin._.installed = true
          self.plugin._.dirty = true
        end
      end,
    })
  end,
}

-- setup origin branches if needed
-- fetch will retrieve the data
M.branch = {
  skip = function(plugin)
    if not plugin._.installed or plugin._.is_local then
      return true
    end
    local branch = assert(Git.get_branch(plugin))
    return Git.get_commit(plugin.dir, branch)
  end,
  run = function(self)
    local args = {
      "remote",
      "set-branches",
      "--add",
      "origin",
      assert(Git.get_branch(self.plugin)),
    }

    self:spawn("git", {
      args = args,
      cwd = self.plugin.dir,
    })
  end,
}

-- fetches all needed origin branches
M.fetch = {
  skip = function(plugin)
    return not plugin._.installed or plugin._.is_local
  end,

  run = function(self)
    local args = {
      "fetch",
      "--recurse-submodules",
      "--update-shallow",
      "--progress",
    }

    self:spawn("git", {
      args = args,
      cwd = self.plugin.dir,
    })
  end,
}

-- checkout to the target commit
-- branches will exists at this point, so so will the commit
M.checkout = {
  skip = function(plugin)
    return not plugin._.installed or plugin._.is_local
  end,

  ---@param opts {lockfile?:boolean}
  run = function(self, opts)
    local info = assert(Git.info(self.plugin.dir))
    local target = assert(Git.get_target(self.plugin))

    -- if the plugin is pinned and we did not just clone it,
    -- then don't update
    if self.plugin.pin and not self.plugin._.cloned then
      target = info
    end

    local lock
    if opts.lockfile then
      -- restore to the lock if it exists
      lock = Lock.get(self.plugin)
      if lock then
        ---@diagnostic disable-next-line: cast-local-type
        target = lock
      end
    end

    -- dont run checkout if target is already reached.
    -- unless we just clones, since then we won't have any data yet
    if not self.plugin._.cloned and info.commit == target.commit and info.branch == target.branch then
      self.plugin._.updated = {
        from = info.commit,
        to = info.commit,
      }
      return
    end

    local args = {
      "checkout",
      "--progress",
    }

    if lock then
      table.insert(args, lock.commit)
    elseif target.tag then
      table.insert(args, "tags/" .. target.tag)
    elseif self.plugin.commit then
      table.insert(args, self.plugin.commit)
    else
      table.insert(args, target.commit)
    end

    self:spawn("git", {
      args = args,
      cwd = self.plugin.dir,
      on_exit = function(ok)
        if ok then
          local new_info = assert(Git.info(self.plugin.dir))
          if not self.plugin._.cloned then
            self.plugin._.updated = {
              from = info.commit,
              to = new_info.commit,
            }
          end
          self.plugin._.dirty = true
        end
      end,
    })
  end,
}
return M