/* replacement start */
const AbortController = globalThis.AbortController || require('abort-controller').AbortController
const AbortSignal = globalThis.AbortSignal || require('abort-controller').AbortSignal
const EventTarget = globalThis.EventTarget || require('event-target-shim').EventTarget
if (typeof AbortSignal.abort !== 'function') {
  AbortSignal.abort = function () {
    const controller = new AbortController()
    controller.abort()
    return controller.signal
  }
}
/* replacement end */

;('use strict')
const tap = require('tap')
const silentConsole = {
  log() {},
  error() {}
}
const common = require('../common')
const { Readable, addAbortSignal } = require('../../lib/ours/index')
const assert = require('assert')
{
  const read = new Readable({
    read() {}
  })
  read.resume()
  read.on('close', common.mustCall())
  read.destroy()
  assert.strictEqual(read.errored, null)
  assert.strictEqual(read.destroyed, true)
}
{
  const read = new Readable({
    read() {}
  })
  read.resume()
  const expected = new Error('kaboom')
  read.on('end', common.mustNotCall('no end event'))
  read.on('close', common.mustCall())
  read.on(
    'error',
    common.mustCall((err) => {
      assert.strictEqual(err, expected)
    })
  )
  read.destroy(expected)
  assert.strictEqual(read.errored, expected)
  assert.strictEqual(read.destroyed, true)
}
{
  const read = new Readable({
    read() {}
  })
  read._destroy = common.mustCall(function (err, cb) {
    assert.strictEqual(err, expected)
    cb(err)
  })
  const expected = new Error('kaboom')
  read.on('end', common.mustNotCall('no end event'))
  read.on('close', common.mustCall())
  read.on(
    'error',
    common.mustCall((err) => {
      assert.strictEqual(err, expected)
    })
  )
  read.destroy(expected)
  assert.strictEqual(read.destroyed, true)
}
{
  const read = new Readable({
    read() {},
    destroy: common.mustCall(function (err, cb) {
      assert.strictEqual(err, expected)
      cb()
    })
  })
  const expected = new Error('kaboom')
  read.on('end', common.mustNotCall('no end event'))

  // Error is swallowed by the custom _destroy
  read.on('error', common.mustNotCall('no error event'))
  read.on('close', common.mustCall())
  read.destroy(expected)
  assert.strictEqual(read.destroyed, true)
}
{
  const read = new Readable({
    read() {}
  })
  read._destroy = common.mustCall(function (err, cb) {
    assert.strictEqual(err, null)
    cb()
  })
  read.destroy()
  assert.strictEqual(read.destroyed, true)
}
{
  const read = new Readable({
    read() {}
  })
  read.resume()
  read._destroy = common.mustCall(function (err, cb) {
    assert.strictEqual(err, null)
    process.nextTick(() => {
      this.push(null)
      cb()
    })
  })
  const fail = common.mustNotCall('no end event')
  read.on('end', fail)
  read.on('close', common.mustCall())
  read.destroy()
  read.removeListener('end', fail)
  read.on('end', common.mustNotCall())
  assert.strictEqual(read.destroyed, true)
}
{
  const read = new Readable({
    read() {}
  })
  const expected = new Error('kaboom')
  read._destroy = common.mustCall(function (err, cb) {
    assert.strictEqual(err, null)
    cb(expected)
  })
  let ticked = false
  read.on('end', common.mustNotCall('no end event'))
  read.on(
    'error',
    common.mustCall((err) => {
      assert.strictEqual(ticked, true)
      assert.strictEqual(read._readableState.errorEmitted, true)
      assert.strictEqual(read._readableState.errored, expected)
      assert.strictEqual(err, expected)
    })
  )
  read.destroy()
  assert.strictEqual(read._readableState.errorEmitted, false)
  assert.strictEqual(read._readableState.errored, expected)
  assert.strictEqual(read.destroyed, true)
  ticked = true
}
{
  const read = new Readable({
    read() {}
  })
  read.resume()
  read.destroyed = true
  assert.strictEqual(read.destroyed, true)

  // The internal destroy() mechanism should not be triggered
  read.on('end', common.mustNotCall())
  read.destroy()
}
{
  function MyReadable() {
    assert.strictEqual(this.destroyed, false)
    this.destroyed = false
    Readable.call(this)
  }
  Object.setPrototypeOf(MyReadable.prototype, Readable.prototype)
  Object.setPrototypeOf(MyReadable, Readable)
  new MyReadable()
}
{
  // Destroy and destroy callback
  const read = new Readable({
    read() {}
  })
  read.resume()
  const expected = new Error('kaboom')
  let ticked = false
  read.on(
    'close',
    common.mustCall(() => {
      assert.strictEqual(read._readableState.errorEmitted, true)
      assert.strictEqual(ticked, true)
    })
  )
  read.on(
    'error',
    common.mustCall((err) => {
      assert.strictEqual(err, expected)
    })
  )
  assert.strictEqual(read._readableState.errored, null)
  assert.strictEqual(read._readableState.errorEmitted, false)
  read.destroy(
    expected,
    common.mustCall(function (err) {
      assert.strictEqual(read._readableState.errored, expected)
      assert.strictEqual(err, expected)
    })
  )
  assert.strictEqual(read._readableState.errorEmitted, false)
  assert.strictEqual(read._readableState.errored, expected)
  ticked = true
}
{
  const readable = new Readable({
    destroy: common.mustCall(function (err, cb) {
      process.nextTick(cb, new Error('kaboom 1'))
    }),
    read() {}
  })
  let ticked = false
  readable.on(
    'close',
    common.mustCall(() => {
      assert.strictEqual(ticked, true)
      assert.strictEqual(readable._readableState.errorEmitted, true)
    })
  )
  readable.on(
    'error',
    common.mustCall((err) => {
      assert.strictEqual(ticked, true)
      assert.strictEqual(err.message, 'kaboom 1')
      assert.strictEqual(readable._readableState.errorEmitted, true)
    })
  )
  readable.destroy()
  assert.strictEqual(readable.destroyed, true)
  assert.strictEqual(readable._readableState.errored, null)
  assert.strictEqual(readable._readableState.errorEmitted, false)

  // Test case where `readable.destroy()` is called again with an error before
  // the `_destroy()` callback is called.
  readable.destroy(new Error('kaboom 2'))
  assert.strictEqual(readable._readableState.errorEmitted, false)
  assert.strictEqual(readable._readableState.errored, null)
  ticked = true
}
{
  const read = new Readable({
    read() {}
  })
  read.destroy()
  read.push('hi')
  read.on('data', common.mustNotCall())
}
{
  const read = new Readable({
    read: common.mustNotCall()
  })
  read.destroy()
  assert.strictEqual(read.destroyed, true)
  read.read()
}
{
  const read = new Readable({
    autoDestroy: false,
    read() {
      this.push(null)
      this.push('asd')
    }
  })
  read.on(
    'error',
    common.mustCall(() => {
      assert(read._readableState.errored)
    })
  )
  read.resume()
}
{
  const controller = new AbortController()
  const read = addAbortSignal(
    controller.signal,
    new Readable({
      read() {
        this.push('asd')
      }
    })
  )
  read.on(
    'error',
    common.mustCall((e) => {
      assert.strictEqual(e.name, 'AbortError')
    })
  )
  controller.abort()
  read.on('data', common.mustNotCall())
}
{
  const controller = new AbortController()
  const read = new Readable({
    signal: controller.signal,
    read() {
      this.push('asd')
    }
  })
  read.on(
    'error',
    common.mustCall((e) => {
      assert.strictEqual(e.name, 'AbortError')
    })
  )
  controller.abort()
  read.on('data', common.mustNotCall())
}
{
  const controller = new AbortController()
  const read = addAbortSignal(
    controller.signal,
    new Readable({
      objectMode: true,
      read() {
        return false
      }
    })
  )
  read.push('asd')
  read.on(
    'error',
    common.mustCall((e) => {
      assert.strictEqual(e.name, 'AbortError')
    })
  )
  assert.rejects(
    (async () => {
      // eslint-disable-next-line no-unused-vars, no-empty
      for await (const chunk of read) {
      }
    })(),
    /AbortError/
  )
  setTimeout(() => controller.abort(), 0)
}
{
  const read = new Readable({
    read() {}
  })
  read.on('data', common.mustNotCall())
  read.on(
    'error',
    common.mustCall((e) => {
      read.push('asd')
      read.read()
    })
  )
  read.on(
    'close',
    common.mustCall((e) => {
      read.push('asd')
      read.read()
    })
  )
  read.destroy(new Error('asd'))
}
{
  const read = new Readable({
    read() {}
  })
  read.on('data', common.mustNotCall())
  read.on(
    'close',
    common.mustCall((e) => {
      read.push('asd')
      read.read()
    })
  )
  read.destroy()
}
{
  const read = new Readable({
    read() {}
  })
  read.on('data', common.mustNotCall())
  read.on(
    'close',
    common.mustCall((e) => {
      read.push('asd')
      read.unshift('asd')
    })
  )
  read.destroy()
}
{
  const read = new Readable({
    read() {}
  })
  read.on('data', common.mustNotCall())
  read.destroy()
  read.unshift('asd')
}
{
  const read = new Readable({
    read() {}
  })
  read.resume()
  read.on('data', common.mustNotCall())
  read.on(
    'close',
    common.mustCall((e) => {
      read.push('asd')
    })
  )
  read.destroy()
}
{
  const read = new Readable({
    read() {}
  })
  read.on('data', common.mustNotCall())
  read.destroy()
  read.push('asd')
}

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