/**
 * Base Transform Stream
 *
 * Provides a base class for Transform streams.
 * Works identically in both browser and Node.js environments.
 */
import { EventEmitter } from "./event-emitter.js";
/**
 * Browser-compatible Base Transform stream
 */
export class BaseTransform extends EventEmitter {
    constructor(options = {}) {
        super();
        this._buffer = [];
        this._bufferIndex = 0;
        this._isProcessing = false;
        this._isDestroyed = false;
        this._isFinished = false;
        this._errorEmitted = false;
        this._ending = false;
        this._flushInProgress = false;
        this._objectMode = options.objectMode ?? false;
        this._highWaterMark = options.highWaterMark ?? 16384;
    }
    /**
     * Optional flush implementation for remaining data
     */
    processFlush(callback) {
        callback();
    }
    /**
     * Write data to the transform stream
     */
    write(chunk) {
        if (this._isDestroyed) {
            this.emit("error", new Error("Cannot write to destroyed stream"));
            return false;
        }
        this._buffer.push(chunk);
        this._processNext();
        return this._buffer.length - this._bufferIndex < this._highWaterMark;
    }
    /**
     * Signal end of input
     */
    end(chunk) {
        if (chunk !== undefined) {
            this.write(chunk);
        }
        this._ending = true;
        this._maybeFlush();
    }
    /**
     * Destroy the stream
     */
    destroy(error) {
        if (this._isDestroyed) {
            return;
        }
        this._isDestroyed = true;
        // Reuse array if possible
        this._buffer.length = 0;
        this._bufferIndex = 0;
        if (error) {
            this._emitError(error);
        }
        this.emit("close");
    }
    /**
     * Process next chunk in buffer
     */
    _processNext() {
        // Combined early exit check
        if (this._isProcessing || this._isDestroyed || this._bufferIndex >= this._buffer.length) {
            return;
        }
        this._isProcessing = true;
        const chunk = this._buffer[this._bufferIndex++];
        // Reset/compact when we consume a lot of the prefix to avoid unbounded growth.
        if (this._bufferIndex === this._buffer.length) {
            this._buffer.length = 0;
            this._bufferIndex = 0;
        }
        else if (this._bufferIndex > 1024 && this._bufferIndex * 2 > this._buffer.length) {
            this._buffer = this._buffer.slice(this._bufferIndex);
            this._bufferIndex = 0;
        }
        try {
            this.processChunk(chunk, (err, data) => {
                this._isProcessing = false;
                if (err) {
                    this._emitError(err);
                }
                else {
                    if (data !== undefined) {
                        this.emit("data", data);
                    }
                    // Process next chunk
                    this._processNext();
                    this._maybeFlush();
                }
            });
        }
        catch (err) {
            this._isProcessing = false;
            this._emitError(err);
            this._maybeFlush();
        }
    }
    _maybeFlush() {
        if (!this._ending || this._isDestroyed || this._isFinished || this._flushInProgress) {
            return;
        }
        // If an error was emitted, stop flushing to avoid hanging end().
        if (this._errorEmitted) {
            this._isFinished = true;
            this.emit("finish");
            this.emit("end");
            return;
        }
        if (this._isProcessing || this._bufferIndex < this._buffer.length) {
            return;
        }
        this._flushInProgress = true;
        this.processFlush((err, data) => {
            this._flushInProgress = false;
            if (err) {
                this._emitError(err);
            }
            else if (data !== undefined) {
                this.emit("data", data);
            }
            this._isFinished = true;
            this.emit("finish");
            this.emit("end");
        });
    }
    /**
     * Emit error only once
     */
    _emitError(error) {
        if (!this._errorEmitted) {
            this._errorEmitted = true;
            this.emit("error", error);
        }
    }
    /**
     * Check if stream is in a valid state
     */
    get isValid() {
        return !this._isDestroyed && !this._errorEmitted;
    }
    /**
     * Check if stream has finished
     */
    get isFinished() {
        return this._isFinished;
    }
    /**
     * Pipe to another transform or collector
     */
    pipe(destination) {
        this.on("data", chunk => {
            destination.write(chunk);
        });
        this.on("end", () => {
            destination.end();
        });
        this.on("error", err => {
            destination.destroy(err);
        });
        return destination;
    }
}
