require("async.test")

local Promise = require("promise")

describe("Promise.try_each", function()
  async_it("resolves with the first successful promise", function(done)
    local promise1 = Promise:new(function(_, reject)
      vim.defer_fn(function()
        reject("Failed 1")
      end, 100)
    end)

    local promise2 = Promise:new(function(resolve)
      vim.defer_fn(function()
        resolve("Success 2")
      end, 200)
    end)

    local promise3 = Promise:new(function(resolve)
      vim.defer_fn(function()
        resolve("Success 3")
      end, 300)
    end)

    Promise.try_each({ promise1, promise2, promise3 })
      :and_then(function(result)
        assert.are.equal("Success 2", result)
        done()
      end)
      :catch(function()
        error("try_each should not reject if there is a successful promise.")
      end)
  end)

  async_it("rejects with the last error if all promises fail", function(done)
    local promise1 = Promise:new(function(_, reject)
      vim.defer_fn(function()
        reject("Failed 1")
      end, 100)
    end)

    local promise2 = Promise:new(function(_, reject)
      vim.defer_fn(function()
        reject("Failed 2")
      end, 200)
    end)

    local promise3 = Promise:new(function(_, reject)
      vim.defer_fn(function()
        reject("Failed 3")
      end, 300)
    end)

    Promise.try_each({ promise1, promise2, promise3 })
      :and_then(function()
        error("try_each should not resolve if all promises fail.")
      end)
      :catch(function(reason)
        assert.are.equal("Failed 3", reason)
        done()
      end)
  end)

  async_it("resolves immediately with the first promise if it is successful", function(done)
    local promise1 = Promise:new(function(resolve)
      vim.defer_fn(function()
        resolve("Immediate Success")
      end, 100)
    end)

    local promise2 = Promise:new(function(_, reject)
      vim.defer_fn(function()
        reject("Failed 2")
      end, 200)
    end)

    local promise3 = Promise:new(function(_, reject)
      vim.defer_fn(function()
        reject("Failed 3")
      end, 300)
    end)

    Promise.try_each({ promise1, promise2, promise3 })
      :and_then(function(result)
        assert.are.equal("Immediate Success", result)
        done()
      end)
      :catch(function()
        error("try_each should not reject if the first promise is successful.")
      end)
  end)

  async_it("rejects immediately if the list is empty", function(done)
    Promise.try_each({})
      :and_then(function()
        error("try_each should not resolve if the list is empty.")
      end)
      :catch(function(reason)
        assert.are.equal("No promises to try.", reason)
        done()
      end)
  end)

  async_it("resolves with the first successful promise after some rejections", function(done)
    local promise1 = Promise:new(function(_, reject)
      vim.defer_fn(function()
        reject("Failed 1")
      end, 100)
    end)

    local promise2 = Promise:new(function(_, reject)
      vim.defer_fn(function()
        reject("Failed 2")
      end, 200)
    end)

    local promise3 = Promise:new(function(resolve)
      vim.defer_fn(function()
        resolve("Success 3")
      end, 300)
    end)

    local promise4 = Promise:new(function(_, reject)
      vim.defer_fn(function()
        reject("Failed 4")
      end, 400)
    end)

    Promise.try_each({ promise1, promise2, promise3, promise4 })
      :and_then(function(result)
        assert.are.equal("Success 3", result)
        done()
      end)
      :catch(function()
        error("try_each should not reject if one of the promises is successful.")
      end)
  end)

  async_it("handles single successful promise correctly", function(done)
    local promise = Promise:new(function(resolve)
      vim.defer_fn(function()
        resolve("Single Success")
      end, 100)
    end)

    Promise.try_each({ promise })
      :and_then(function(result)
        assert.are.equal("Single Success", result)
        done()
      end)
      :catch(function()
        error("try_each should not reject for a single successful promise.")
      end)
  end)

  async_it("handles single rejected promise correctly", function(done)
    local promise = Promise:new(function(_, reject)
      vim.defer_fn(function()
        reject("Single Failure")
      end, 100)
    end)

    Promise.try_each({ promise })
      :and_then(function()
        error("try_each should not resolve if the single promise is rejected.")
      end)
      :catch(function(reason)
        assert.are.equal("Single Failure", reason)
        done()
      end)
  end)
end)
