"use strict";
/**
 * Pull Stream
 *
 * A stream that allows pulling data from internal buffer with pattern matching.
 * Works identically in both browser and Node.js environments.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.PullStream = void 0;
const event_emitter_1 = require("./event-emitter.js");
const shared_1 = require("./shared.js");
/**
 * Browser-compatible Pull Stream - Read data from buffer on demand with pattern matching
 */
class PullStream extends event_emitter_1.EventEmitter {
    constructor(_options = {}) {
        super();
        // Use chunked buffer storage to avoid repeated concat
        this._bufferChunks = [];
        this._totalLength = 0;
        this.finished = false;
        this._destroyed = false;
    }
    // Consolidate chunks into single buffer when needed
    get buffer() {
        const len = this._bufferChunks.length;
        if (len === 0) {
            return new Uint8Array(0);
        }
        if (len === 1) {
            return this._bufferChunks[0];
        }
        // Consolidate multiple chunks
        const buf = (0, shared_1.concatUint8Arrays)(this._bufferChunks);
        this._bufferChunks = [buf];
        return buf;
    }
    set buffer(buf) {
        this._bufferChunks = buf.length > 0 ? [buf] : [];
        this._totalLength = buf.length;
    }
    /**
     * Write data to the stream
     */
    write(chunk) {
        if (this._destroyed) {
            this.emit("error", new Error("Cannot write to destroyed stream"));
            return false;
        }
        this._bufferChunks.push(chunk);
        this._totalLength += chunk.length;
        this.emit("chunk");
        return true;
    }
    /**
     * Signal end of input
     */
    end(chunk) {
        if (chunk !== undefined) {
            this.write(chunk);
        }
        this.finished = true;
        this.emit("chunk", false);
        this.emit("finish");
        this.emit("end");
    }
    /**
     * Destroy the stream
     */
    destroy(error) {
        if (this._destroyed) {
            return;
        }
        this._destroyed = true;
        this._bufferChunks = [];
        this._totalLength = 0;
        if (error) {
            this.emit("error", error);
        }
        this.emit("close");
    }
    pull(sizeOrPattern, includePattern) {
        if (typeof sizeOrPattern === "number") {
            return this._pullSize(sizeOrPattern);
        }
        return this._pullPattern(sizeOrPattern, includePattern ?? false);
    }
    /**
     * Pull until pattern is found (alias for pull(pattern, includePattern))
     */
    pullUntil(pattern, includePattern) {
        return this._pullPattern(pattern, includePattern ?? false);
    }
    _pullSize(size) {
        return new Promise((resolve, reject) => {
            const tryPull = () => {
                if (this._destroyed) {
                    reject(new Error("Stream destroyed"));
                    return;
                }
                // Use _totalLength for fast check before consolidating
                if (this._totalLength >= size) {
                    const buf = this.buffer;
                    const result = buf.subarray(0, size);
                    this.buffer = buf.subarray(size);
                    resolve(result);
                    return;
                }
                if (this.finished) {
                    // Return whatever we have
                    const result = this.buffer;
                    this._bufferChunks = [];
                    this._totalLength = 0;
                    resolve(result);
                    return;
                }
                // Wait for more data
                this.once("chunk", tryPull);
            };
            tryPull();
        });
    }
    _pullPattern(pattern, includePattern) {
        return new Promise((resolve, reject) => {
            const tryPull = () => {
                if (this._destroyed) {
                    reject(new Error("Stream destroyed"));
                    return;
                }
                const buf = this.buffer;
                const matchIndex = this._indexOf(buf, pattern);
                if (matchIndex !== -1) {
                    this._match = matchIndex;
                    const endIndex = includePattern ? matchIndex + pattern.length : matchIndex;
                    const result = buf.subarray(0, endIndex);
                    this.buffer = buf.subarray(includePattern ? endIndex : matchIndex + pattern.length);
                    resolve(result);
                    return;
                }
                if (this.finished) {
                    // Pattern not found, return everything
                    const result = buf;
                    this._bufferChunks = [];
                    this._totalLength = 0;
                    resolve(result);
                    return;
                }
                // Wait for more data
                this.once("chunk", tryPull);
            };
            tryPull();
        });
    }
    /**
     * Get the match position from last pattern match
     */
    get matchPosition() {
        return this._match;
    }
    /**
     * Get remaining buffer length
     */
    get length() {
        return this._totalLength;
    }
    /**
     * Check if stream is finished
     */
    get isFinished() {
        return this.finished;
    }
    /**
     * Check if stream is destroyed
     */
    get destroyed() {
        return this._destroyed;
    }
    /**
     * Find pattern in Uint8Array (like Buffer.indexOf)
     */
    _indexOf(haystack, needle, start = 0) {
        const needleLen = needle.length;
        if (needleLen === 0) {
            return start;
        }
        const haystackLen = haystack.length;
        if (needleLen > haystackLen) {
            return -1;
        }
        const first = needle[0];
        const last = haystackLen - needleLen;
        outer: for (let i = start; i <= last; i++) {
            // Quick check first byte
            if (haystack[i] !== first) {
                continue;
            }
            // Check rest of pattern
            for (let j = 1; j < needleLen; j++) {
                if (haystack[i + j] !== needle[j]) {
                    continue outer;
                }
            }
            return i;
        }
        return -1;
    }
}
exports.PullStream = PullStream;
