/* 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 { Duplex } = require('../../lib/ours/index')
const assert = require('assert')
{
  const duplex = new Duplex({
    write(chunk, enc, cb) {
      cb()
    },
    read() {}
  })
  duplex.resume()
  duplex.on('end', common.mustNotCall())
  duplex.on('finish', common.mustNotCall())
  duplex.on('close', common.mustCall())
  duplex.destroy()
  assert.strictEqual(duplex.destroyed, true)
}
{
  const duplex = new Duplex({
    write(chunk, enc, cb) {
      cb()
    },
    read() {}
  })
  duplex.resume()
  const expected = new Error('kaboom')
  duplex.on('end', common.mustNotCall())
  duplex.on('finish', common.mustNotCall())
  duplex.on(
    'error',
    common.mustCall((err) => {
      assert.strictEqual(err, expected)
    })
  )
  duplex.destroy(expected)
  assert.strictEqual(duplex.destroyed, true)
}
{
  const duplex = new Duplex({
    write(chunk, enc, cb) {
      cb()
    },
    read() {}
  })
  duplex._destroy = common.mustCall(function (err, cb) {
    assert.strictEqual(err, expected)
    cb(err)
  })
  const expected = new Error('kaboom')
  duplex.on('finish', common.mustNotCall('no finish event'))
  duplex.on(
    'error',
    common.mustCall((err) => {
      assert.strictEqual(err, expected)
    })
  )
  duplex.destroy(expected)
  assert.strictEqual(duplex.destroyed, true)
}
{
  const expected = new Error('kaboom')
  const duplex = new Duplex({
    write(chunk, enc, cb) {
      cb()
    },
    read() {},
    destroy: common.mustCall(function (err, cb) {
      assert.strictEqual(err, expected)
      cb()
    })
  })
  duplex.resume()
  duplex.on('end', common.mustNotCall('no end event'))
  duplex.on('finish', common.mustNotCall('no finish event'))

  // Error is swallowed by the custom _destroy
  duplex.on('error', common.mustNotCall('no error event'))
  duplex.on('close', common.mustCall())
  duplex.destroy(expected)
  assert.strictEqual(duplex.destroyed, true)
}
{
  const duplex = new Duplex({
    write(chunk, enc, cb) {
      cb()
    },
    read() {}
  })
  duplex._destroy = common.mustCall(function (err, cb) {
    assert.strictEqual(err, null)
    cb()
  })
  duplex.destroy()
  assert.strictEqual(duplex.destroyed, true)
}
{
  const duplex = new Duplex({
    write(chunk, enc, cb) {
      cb()
    },
    read() {}
  })
  duplex.resume()
  duplex._destroy = common.mustCall(function (err, cb) {
    assert.strictEqual(err, null)
    process.nextTick(() => {
      this.push(null)
      this.end()
      cb()
    })
  })
  const fail = common.mustNotCall('no finish or end event')
  duplex.on('finish', fail)
  duplex.on('end', fail)
  duplex.destroy()
  duplex.removeListener('end', fail)
  duplex.removeListener('finish', fail)
  duplex.on('end', common.mustNotCall())
  duplex.on('finish', common.mustNotCall())
  assert.strictEqual(duplex.destroyed, true)
}
{
  const duplex = new Duplex({
    write(chunk, enc, cb) {
      cb()
    },
    read() {}
  })
  const expected = new Error('kaboom')
  duplex._destroy = common.mustCall(function (err, cb) {
    assert.strictEqual(err, null)
    cb(expected)
  })
  duplex.on('finish', common.mustNotCall('no finish event'))
  duplex.on('end', common.mustNotCall('no end event'))
  duplex.on(
    'error',
    common.mustCall((err) => {
      assert.strictEqual(err, expected)
    })
  )
  duplex.destroy()
  assert.strictEqual(duplex.destroyed, true)
}
{
  const duplex = new Duplex({
    write(chunk, enc, cb) {
      cb()
    },
    read() {},
    allowHalfOpen: true
  })
  duplex.resume()
  duplex.on('finish', common.mustNotCall())
  duplex.on('end', common.mustNotCall())
  duplex.destroy()
  assert.strictEqual(duplex.destroyed, true)
}
{
  const duplex = new Duplex({
    write(chunk, enc, cb) {
      cb()
    },
    read() {}
  })
  duplex.destroyed = true
  assert.strictEqual(duplex.destroyed, true)

  // The internal destroy() mechanism should not be triggered
  duplex.on('finish', common.mustNotCall())
  duplex.on('end', common.mustNotCall())
  duplex.destroy()
}
{
  function MyDuplex() {
    assert.strictEqual(this.destroyed, false)
    this.destroyed = false
    Duplex.call(this)
  }
  Object.setPrototypeOf(MyDuplex.prototype, Duplex.prototype)
  Object.setPrototypeOf(MyDuplex, Duplex)
  new MyDuplex()
}
{
  const duplex = new Duplex({
    writable: false,
    autoDestroy: true,
    write(chunk, enc, cb) {
      cb()
    },
    read() {}
  })
  duplex.push(null)
  duplex.resume()
  duplex.on('close', common.mustCall())
}
{
  const duplex = new Duplex({
    readable: false,
    autoDestroy: true,
    write(chunk, enc, cb) {
      cb()
    },
    read() {}
  })
  duplex.end()
  duplex.on('close', common.mustCall())
}
{
  const duplex = new Duplex({
    allowHalfOpen: false,
    autoDestroy: true,
    write(chunk, enc, cb) {
      cb()
    },
    read() {}
  })
  duplex.push(null)
  duplex.resume()
  const orgEnd = duplex.end
  duplex.end = common.mustNotCall()
  duplex.on('end', () => {
    // Ensure end() is called in next tick to allow
    // any pending writes to be invoked first.
    process.nextTick(() => {
      duplex.end = common.mustCall(orgEnd)
    })
  })
  duplex.on('close', common.mustCall())
}
{
  // Check abort signal
  const controller = new AbortController()
  const { signal } = controller
  const duplex = new Duplex({
    write(chunk, enc, cb) {
      cb()
    },
    read() {},
    signal
  })
  let count = 0
  duplex.on(
    'error',
    common.mustCall((e) => {
      assert.strictEqual(count++, 0) // Ensure not called twice
      assert.strictEqual(e.name, 'AbortError')
    })
  )
  duplex.on('close', common.mustCall())
  controller.abort()
}

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