'use strict'

const tap = require('tap')
const silentConsole = {
  log() {},
  error() {}
}
const { mustCall, mustNotCall } = require('../common')
const { Readable } = require('../../lib/ours/index')
const { strictEqual } = require('assert')
async function asyncSupport() {
  const finallyMustCall = mustCall()
  const bodyMustCall = mustCall()
  async function* infiniteGenerate() {
    try {
      while (true) yield 'a'
    } finally {
      finallyMustCall()
    }
  }
  const stream = Readable.from(infiniteGenerate())
  for await (const chunk of stream) {
    bodyMustCall()
    strictEqual(chunk, 'a')
    break
  }
}
async function syncSupport() {
  const finallyMustCall = mustCall()
  const bodyMustCall = mustCall()
  function* infiniteGenerate() {
    try {
      while (true) yield 'a'
    } finally {
      finallyMustCall()
    }
  }
  const stream = Readable.from(infiniteGenerate())
  for await (const chunk of stream) {
    bodyMustCall()
    strictEqual(chunk, 'a')
    break
  }
}
async function syncPromiseSupport() {
  const returnMustBeAwaited = mustCall()
  const bodyMustCall = mustCall()
  function* infiniteGenerate() {
    try {
      while (true) yield Promise.resolve('a')
    } finally {
      // eslint-disable-next-line no-unsafe-finally
      return {
        then(cb) {
          returnMustBeAwaited()
          cb()
        }
      }
    }
  }
  const stream = Readable.from(infiniteGenerate())
  for await (const chunk of stream) {
    bodyMustCall()
    strictEqual(chunk, 'a')
    break
  }
}
async function syncRejectedSupport() {
  const returnMustBeAwaited = mustCall()
  const bodyMustNotCall = mustNotCall()
  const catchMustCall = mustCall()
  const secondNextMustNotCall = mustNotCall()
  function* generate() {
    try {
      yield Promise.reject('a')
      secondNextMustNotCall()
    } finally {
      // eslint-disable-next-line no-unsafe-finally
      return {
        then(cb) {
          returnMustBeAwaited()
          cb()
        }
      }
    }
  }
  const stream = Readable.from(generate())
  try {
    for await (const chunk of stream) {
      bodyMustNotCall(chunk)
    }
  } catch {
    catchMustCall()
  }
}
async function noReturnAfterThrow() {
  const returnMustNotCall = mustNotCall()
  const bodyMustNotCall = mustNotCall()
  const catchMustCall = mustCall()
  const nextMustCall = mustCall()
  const stream = Readable.from({
    [Symbol.asyncIterator]() {
      return this
    },
    async next() {
      nextMustCall()
      throw new Error('a')
    },
    async return() {
      returnMustNotCall()
      return {
        done: true
      }
    }
  })
  try {
    for await (const chunk of stream) {
      bodyMustNotCall(chunk)
    }
  } catch {
    catchMustCall()
  }
}
async function closeStreamWhileNextIsPending() {
  const finallyMustCall = mustCall()
  const dataMustCall = mustCall()
  let resolveDestroy
  const destroyed = new Promise((resolve) => {
    resolveDestroy = mustCall(resolve)
  })
  let resolveYielded
  const yielded = new Promise((resolve) => {
    resolveYielded = mustCall(resolve)
  })
  async function* infiniteGenerate() {
    try {
      while (true) {
        yield 'a'
        resolveYielded()
        await destroyed
      }
    } finally {
      finallyMustCall()
    }
  }
  const stream = Readable.from(infiniteGenerate())
  stream.on('data', (data) => {
    dataMustCall()
    strictEqual(data, 'a')
  })
  yielded.then(() => {
    stream.destroy()
    resolveDestroy()
  })
}
async function closeAfterNullYielded() {
  const finallyMustCall = mustCall()
  const dataMustCall = mustCall(3)
  function* generate() {
    try {
      yield 'a'
      yield 'a'
      yield 'a'
    } finally {
      finallyMustCall()
    }
  }
  const stream = Readable.from(generate())
  stream.on('data', (chunk) => {
    dataMustCall()
    strictEqual(chunk, 'a')
  })
}
Promise.all([
  asyncSupport(),
  syncSupport(),
  syncPromiseSupport(),
  syncRejectedSupport(),
  noReturnAfterThrow(),
  closeStreamWhileNextIsPending(),
  closeAfterNullYielded()
]).then(mustCall())

/* replacement start */
process.on('beforeExit', (code) => {
  if (code === 0) {
    tap.pass('test succeeded')
  } else {
    tap.fail(`test failed - exited code ${code}`)
  }
})
/* replacement end */
