import stream from 'node:stream';

const TransformStream = stream.Transform;

class ProgressStream extends TransformStream {
    #options;
    #transferred;
    #delta;
    #started;
    #startTime;
    #interval;
    #heartbeatInterval;

    constructor(options) {
        super();
        this.#options = Object.assign({ interval: 10 * 1000, heartbeatInterval: 60 * 1000 }, options);
        this.#transferred = 0;
        this.#delta = 0;
        this.#started = false;
        this.#startTime = null;
        this.#interval = null;
        this.#heartbeatInterval = null;
    }

    stats() {
        const duration = Date.now() - this.#startTime; // this is not at _stop time because other streams in pipeline might have to be taken into account
        return { startTime: this.#startTime, duration, transferred: this.#transferred };
    }

    _start() {
        this.#startTime = Date.now();
        this.#started = true;
        this.#interval = setInterval(() => {
            const speed = this.#delta * 1000 / this.#options.interval;
            this.#delta = 0;
            this.emit('progress', { speed, transferred: this.#transferred });
        }, this.#options.interval);
        this.#heartbeatInterval = setInterval(() => {
            const elapsed = Math.round((Date.now() - this.#startTime) / 1000);
            this.emit('heartbeat', { elapsed, transferred: this.#transferred });
        }, this.#options.heartbeatInterval);
    }

    _stop() {
        clearInterval(this.#interval);
        clearInterval(this.#heartbeatInterval);
    }

    _transform(chunk, encoding, callback) {
        if (!this.#started) this._start();
        this.#transferred += chunk.length;
        this.#delta += chunk.length;
        callback(null, chunk);
    }

    _flush(callback) {
        this._stop();
        callback(null);
    }

    _destroy(error, callback) {
        this._stop();
        callback(error);
  }
}

export default ProgressStream;
