local M = {}

---@alias ProcessOpts {args: string[], cwd?: string, on_line?:fun(string), on_exit?: fun(ok:boolean, output:string)}

function M.spawn(cmd, opts)
  opts = opts or {}
  local env = {
    "GIT_TERMINAL_PROMPT=0",
    "GIT_SSH_COMMAND=ssh -oBatchMode=yes",
  }

  for key, value in
    pairs(vim.loop.os_environ() --[[@as string[] ]])
  do
    table.insert(env, key .. "=" .. value)
  end

  local stdout = vim.loop.new_pipe()
  local stderr = vim.loop.new_pipe()

  local output = ""
  ---@type vim.loop.Process
  local handle = nil

  handle = vim.loop.spawn(cmd, {
    stdio = { nil, stdout, stderr },
    args = opts.args,
    cwd = opts.cwd,
    env = env,
  }, function(exit_code)
    handle:close()
    stdout:close()
    stderr:close()
    local check = vim.loop.new_check()
    check:start(function()
      if not stdout:is_closing() or not stderr:is_closing() then
        return
      end
      check:stop()
      if opts.on_exit then
        output = output:gsub("[^\r\n]+\r", "")

        vim.schedule(function()
          opts.on_exit(exit_code == 0, output)
        end)
      end
    end)
  end)

  if not handle then
    if opts.on_exit then
      opts.on_exit(false, "Failed to spawn process " .. cmd .. " " .. vim.inspect(opts))
    end

    return
  end

  local function on_output(err, data)
    assert(not err, err)

    if data then
      output = output .. data:gsub("\r\n", "\n")
      local lines = vim.split(vim.trim(output:gsub("\r$", "")):gsub("[^\n\r]+\r", ""), "\n")

      if opts.on_line then
        vim.schedule(function()
          opts.on_line(lines[#lines])
        end)
      end
    end
  end

  vim.loop.read_start(stdout, on_output)
  vim.loop.read_start(stderr, on_output)

  return handle
end

-- FIXME: can be removed?
function M.all_done(slot0)
  for slot4, slot5 in ipairs(slot0) do
    if slot5 and not slot5:is_closing() then
      return false
    end
  end

  return true
end

return M