1const Stream = require('stream')
2
3class MuteStream extends Stream {
4  #isTTY = null
5
6  constructor (opts = {}) {
7    super(opts)
8    this.writable = this.readable = true
9    this.muted = false
10    this.on('pipe', this._onpipe)
11    this.replace = opts.replace
12
13    // For readline-type situations
14    // This much at the start of a line being redrawn after a ctrl char
15    // is seen (such as backspace) won't be redrawn as the replacement
16    this._prompt = opts.prompt || null
17    this._hadControl = false
18  }
19
20  #destSrc (key, def) {
21    if (this._dest) {
22      return this._dest[key]
23    }
24    if (this._src) {
25      return this._src[key]
26    }
27    return def
28  }
29
30  #proxy (method, ...args) {
31    if (typeof this._dest?.[method] === 'function') {
32      this._dest[method](...args)
33    }
34    if (typeof this._src?.[method] === 'function') {
35      this._src[method](...args)
36    }
37  }
38
39  get isTTY () {
40    if (this.#isTTY !== null) {
41      return this.#isTTY
42    }
43    return this.#destSrc('isTTY', false)
44  }
45
46  // basically just get replace the getter/setter with a regular value
47  set isTTY (val) {
48    this.#isTTY = val
49  }
50
51  get rows () {
52    return this.#destSrc('rows')
53  }
54
55  get columns () {
56    return this.#destSrc('columns')
57  }
58
59  mute () {
60    this.muted = true
61  }
62
63  unmute () {
64    this.muted = false
65  }
66
67  _onpipe (src) {
68    this._src = src
69  }
70
71  pipe (dest, options) {
72    this._dest = dest
73    return super.pipe(dest, options)
74  }
75
76  pause () {
77    if (this._src) {
78      return this._src.pause()
79    }
80  }
81
82  resume () {
83    if (this._src) {
84      return this._src.resume()
85    }
86  }
87
88  write (c) {
89    if (this.muted) {
90      if (!this.replace) {
91        return true
92      }
93      // eslint-disable-next-line no-control-regex
94      if (c.match(/^\u001b/)) {
95        if (c.indexOf(this._prompt) === 0) {
96          c = c.slice(this._prompt.length)
97          c = c.replace(/./g, this.replace)
98          c = this._prompt + c
99        }
100        this._hadControl = true
101        return this.emit('data', c)
102      } else {
103        if (this._prompt && this._hadControl &&
104          c.indexOf(this._prompt) === 0) {
105          this._hadControl = false
106          this.emit('data', this._prompt)
107          c = c.slice(this._prompt.length)
108        }
109        c = c.toString().replace(/./g, this.replace)
110      }
111    }
112    this.emit('data', c)
113  }
114
115  end (c) {
116    if (this.muted) {
117      if (c && this.replace) {
118        c = c.toString().replace(/./g, this.replace)
119      } else {
120        c = null
121      }
122    }
123    if (c) {
124      this.emit('data', c)
125    }
126    this.emit('end')
127  }
128
129  destroy (...args) {
130    return this.#proxy('destroy', ...args)
131  }
132
133  destroySoon (...args) {
134    return this.#proxy('destroySoon', ...args)
135  }
136
137  close (...args) {
138    return this.#proxy('close', ...args)
139  }
140}
141
142module.exports = MuteStream
143