import { Chunk, Effect, TxChunk } from "effect"
import { describe, expect, it } from "vitest"

describe("TxChunk", () => {
  describe("constructors", () => {
    it("make should create a TxChunk with initial chunk", async () => {
      const program = Effect.tx(Effect.gen(function*() {
        const chunk = Chunk.fromIterable([1, 2, 3])
        const txChunk = yield* TxChunk.make(chunk)
        const result = yield* TxChunk.get(txChunk)
        return result
      }))

      const result = await Effect.runPromise(program)
      expect(Chunk.toReadonlyArray(result)).toEqual([1, 2, 3])
    })

    it("empty should create an empty TxChunk", async () => {
      const program = Effect.tx(Effect.gen(function*() {
        const txChunk = yield* TxChunk.empty<number>()
        const result = yield* TxChunk.get(txChunk)
        return result
      }))

      const result = await Effect.runPromise(program)
      expect(Chunk.toReadonlyArray(result)).toEqual([])
    })

    it("fromIterable should create a TxChunk from an iterable", async () => {
      const program = Effect.tx(Effect.gen(function*() {
        const txChunk = yield* TxChunk.fromIterable([4, 5, 6])
        const result = yield* TxChunk.get(txChunk)
        return result
      }))

      const result = await Effect.runPromise(program)
      expect(Chunk.toReadonlyArray(result)).toEqual([4, 5, 6])
    })
  })

  describe("basic operations", () => {
    it("get should read the current chunk", async () => {
      const program = Effect.tx(Effect.gen(function*() {
        const txChunk = yield* TxChunk.fromIterable([1, 2, 3])
        const result = yield* TxChunk.get(txChunk)
        return result
      }))

      const result = await Effect.runPromise(program)
      expect(Chunk.toReadonlyArray(result)).toEqual([1, 2, 3])
    })

    it("set should replace the entire chunk", async () => {
      const program = Effect.tx(Effect.gen(function*() {
        const txChunk = yield* TxChunk.fromIterable([1, 2, 3])
        const newChunk = Chunk.fromIterable([7, 8, 9])

        yield* TxChunk.set(txChunk, newChunk)

        const result = yield* TxChunk.get(txChunk)
        return result
      }))

      const result = await Effect.runPromise(program)
      expect(Chunk.toReadonlyArray(result)).toEqual([7, 8, 9])
    })

    it("append should add element to the end", async () => {
      const program = Effect.tx(Effect.gen(function*() {
        const txChunk = yield* TxChunk.fromIterable([1, 2, 3])

        yield* TxChunk.append(txChunk, 4)

        const result = yield* TxChunk.get(txChunk)
        return result
      }))

      const result = await Effect.runPromise(program)
      expect(Chunk.toReadonlyArray(result)).toEqual([1, 2, 3, 4])
    })

    it("prepend should add element to the beginning", async () => {
      const program = Effect.tx(Effect.gen(function*() {
        const txChunk = yield* TxChunk.fromIterable([2, 3, 4])

        yield* TxChunk.prepend(txChunk, 1)

        const result = yield* TxChunk.get(txChunk)
        return result
      }))

      const result = await Effect.runPromise(program)
      expect(Chunk.toReadonlyArray(result)).toEqual([1, 2, 3, 4])
    })

    it("size should return the chunk size", async () => {
      const program = Effect.tx(Effect.gen(function*() {
        const txChunk = yield* TxChunk.fromIterable([1, 2, 3])
        const size = yield* TxChunk.size(txChunk)
        return size
      }))

      const result = await Effect.runPromise(program)
      expect(result).toBe(3)
    })

    it("isEmpty should return true for empty chunk", async () => {
      const program = Effect.tx(Effect.gen(function*() {
        const txChunk = yield* TxChunk.empty<number>()
        const isEmpty = yield* TxChunk.isEmpty(txChunk)
        return isEmpty
      }))

      const result = await Effect.runPromise(program)
      expect(result).toBe(true)
    })

    it("isEmpty should return false for non-empty chunk", async () => {
      const program = Effect.tx(Effect.gen(function*() {
        const txChunk = yield* TxChunk.fromIterable([1, 2, 3])
        const isEmpty = yield* TxChunk.isEmpty(txChunk)
        return isEmpty
      }))

      const result = await Effect.runPromise(program)
      expect(result).toBe(false)
    })

    it("isNonEmpty should return false for empty chunk", async () => {
      const program = Effect.tx(Effect.gen(function*() {
        const txChunk = yield* TxChunk.empty<number>()
        const isNonEmpty = yield* TxChunk.isNonEmpty(txChunk)
        return isNonEmpty
      }))

      const result = await Effect.runPromise(program)
      expect(result).toBe(false)
    })

    it("isNonEmpty should return true for non-empty chunk", async () => {
      const program = Effect.tx(Effect.gen(function*() {
        const txChunk = yield* TxChunk.fromIterable([1, 2, 3])
        const isNonEmpty = yield* TxChunk.isNonEmpty(txChunk)
        return isNonEmpty
      }))

      const result = await Effect.runPromise(program)
      expect(result).toBe(true)
    })
  })

  describe("transactional semantics", () => {
    it("should perform atomic operations within a transaction", async () => {
      const program = Effect.tx(Effect.gen(function*() {
        const txChunk = yield* TxChunk.fromIterable([1, 2, 3])

        yield* Effect.gen(function*() {
          yield* TxChunk.append(txChunk, 4)
          yield* TxChunk.prepend(txChunk, 0)
        })

        const result = yield* TxChunk.get(txChunk)
        return result
      }))

      const result = await Effect.runPromise(program)
      expect(Chunk.toReadonlyArray(result)).toEqual([0, 1, 2, 3, 4])
    })

    it("modify should transform and return a value atomically", async () => {
      const program = Effect.tx(Effect.gen(function*() {
        const txChunk = yield* TxChunk.fromIterable([1, 2, 3])

        const oldSize = yield* TxChunk.modify(txChunk, (chunk) => [
          Chunk.size(chunk),
          Chunk.append(chunk, 4)
        ])

        const newChunk = yield* TxChunk.get(txChunk)
        return { oldSize, newChunk }
      }))

      const result = await Effect.runPromise(program)
      expect(result.oldSize).toBe(3)
      expect(Chunk.toReadonlyArray(result.newChunk)).toEqual([1, 2, 3, 4])
    })

    it("update should transform the chunk", async () => {
      const program = Effect.tx(Effect.gen(function*() {
        const txChunk = yield* TxChunk.fromIterable([1, 2, 3])

        yield* TxChunk.update(txChunk, (chunk) => Chunk.map(chunk, (x) => x * 2))

        const result = yield* TxChunk.get(txChunk)
        return result
      }))

      const result = await Effect.runPromise(program)
      expect(Chunk.toReadonlyArray(result)).toEqual([2, 4, 6])
    })
  })

  describe("slice operations", () => {
    it("take should keep first n elements", async () => {
      const program = Effect.tx(Effect.gen(function*() {
        const txChunk = yield* TxChunk.fromIterable([1, 2, 3, 4, 5])

        yield* TxChunk.take(txChunk, 3)

        const result = yield* TxChunk.get(txChunk)
        return result
      }))

      const result = await Effect.runPromise(program)
      expect(Chunk.toReadonlyArray(result)).toEqual([1, 2, 3])
    })

    it("drop should remove first n elements", async () => {
      const program = Effect.tx(Effect.gen(function*() {
        const txChunk = yield* TxChunk.fromIterable([1, 2, 3, 4, 5])

        yield* TxChunk.drop(txChunk, 2)

        const result = yield* TxChunk.get(txChunk)
        return result
      }))

      const result = await Effect.runPromise(program)
      expect(Chunk.toReadonlyArray(result)).toEqual([3, 4, 5])
    })

    it("slice should extract elements from start to end", async () => {
      const program = Effect.tx(Effect.gen(function*() {
        const txChunk = yield* TxChunk.fromIterable([1, 2, 3, 4, 5])

        yield* TxChunk.slice(txChunk, 1, 4)

        const result = yield* TxChunk.get(txChunk)
        return result
      }))

      const result = await Effect.runPromise(program)
      expect(Chunk.toReadonlyArray(result)).toEqual([2, 3, 4])
    })
  })

  describe("transform operations", () => {
    it("map should transform each element", async () => {
      const program = Effect.tx(Effect.gen(function*() {
        const txChunk = yield* TxChunk.fromIterable([1, 2, 3])

        yield* TxChunk.map(txChunk, (x) => x * 2)

        const result = yield* TxChunk.get(txChunk)
        return result
      }))

      const result = await Effect.runPromise(program)
      expect(Chunk.toReadonlyArray(result)).toEqual([2, 4, 6])
    })

    it("filter should keep elements that satisfy predicate", async () => {
      const program = Effect.tx(Effect.gen(function*() {
        const txChunk = yield* TxChunk.fromIterable([1, 2, 3, 4, 5])

        yield* TxChunk.filter(txChunk, (x) => x % 2 === 0)

        const result = yield* TxChunk.get(txChunk)
        return result
      }))

      const result = await Effect.runPromise(program)
      expect(Chunk.toReadonlyArray(result)).toEqual([2, 4])
    })
  })

  describe("concatenation operations", () => {
    it("appendAll should concatenate chunk to the end", async () => {
      const program = Effect.tx(Effect.gen(function*() {
        const txChunk = yield* TxChunk.fromIterable([1, 2, 3])
        const other = Chunk.fromIterable([4, 5, 6])

        yield* TxChunk.appendAll(txChunk, other)

        const result = yield* TxChunk.get(txChunk)
        return result
      }))

      const result = await Effect.runPromise(program)
      expect(Chunk.toReadonlyArray(result)).toEqual([1, 2, 3, 4, 5, 6])
    })

    it("prependAll should concatenate chunk to the beginning", async () => {
      const program = Effect.tx(Effect.gen(function*() {
        const txChunk = yield* TxChunk.fromIterable([4, 5, 6])
        const other = Chunk.fromIterable([1, 2, 3])

        yield* TxChunk.prependAll(txChunk, other)

        const result = yield* TxChunk.get(txChunk)
        return result
      }))

      const result = await Effect.runPromise(program)
      expect(Chunk.toReadonlyArray(result)).toEqual([1, 2, 3, 4, 5, 6])
    })

    it("concat should concatenate another TxChunk to the end", async () => {
      const program = Effect.tx(Effect.gen(function*() {
        const txChunk1 = yield* TxChunk.fromIterable([1, 2, 3])
        const txChunk2 = yield* TxChunk.fromIterable([4, 5, 6])

        yield* TxChunk.concat(txChunk1, txChunk2)

        const result = yield* TxChunk.get(txChunk1)
        return result
      }))

      const result = await Effect.runPromise(program)
      expect(Chunk.toReadonlyArray(result)).toEqual([1, 2, 3, 4, 5, 6])
    })

    it("concat should work atomically within transactions", async () => {
      const program = Effect.tx(Effect.gen(function*() {
        const txChunk1 = yield* TxChunk.fromIterable([1, 2])
        const txChunk2 = yield* TxChunk.fromIterable([3, 4])
        const txChunk3 = yield* TxChunk.fromIterable([5, 6])

        yield* Effect.gen(function*() {
          yield* TxChunk.concat(txChunk1, txChunk2)
          yield* TxChunk.concat(txChunk1, txChunk3)
        })

        const result = yield* TxChunk.get(txChunk1)
        return result
      }))

      const result = await Effect.runPromise(program)
      expect(Chunk.toReadonlyArray(result)).toEqual([1, 2, 3, 4, 5, 6])
    })
  })
})
