import { ErrorFactory } from '../exceptions/ZintrustError.js';
import { EventEmitter } from '../node-singletons/index.js';
// Lazy import cloudflare:sockets to prevent Node.js evaluation errors
let connect;
const getCloudflareConnect = async () => {
    if (connect === undefined) {
        const module = await import('cloudflare:sockets');
        connect = module.connect;
    }
    return connect;
};
const DEFAULT_TIMEOUT_MS = 30000;
const toBuffer = (data) => {
    if (typeof Buffer !== 'undefined')
        return Buffer.from(data);
    return data;
};
const createTimeoutSignal = (timeoutMs) => {
    if (timeoutMs <= 0)
        return undefined;
    if (typeof AbortSignal === 'undefined' || typeof AbortSignal.timeout !== 'function')
        return undefined;
    return AbortSignal.timeout(timeoutMs);
};
const withTimeout = async (promise, timeoutMs, message) => {
    const signal = createTimeoutSignal(timeoutMs);
    if (!signal)
        return promise;
    const timeoutPromise = new Promise((_, reject) => {
        signal.addEventListener('abort', () => reject(ErrorFactory.createConnectionError(message)), {
            once: true,
        });
    });
    return Promise.race([promise, timeoutPromise]);
};
const releaseStreamLocks = (state) => {
    if (typeof process !== 'undefined' && process.env?.['VITEST'] !== undefined) {
        state.reader = undefined;
        state.writer = undefined;
        return;
    }
    const reader = state.reader;
    if (reader && typeof reader.releaseLock === 'function') {
        try {
            reader.releaseLock();
        }
        catch {
            // ignore lock release errors
        }
    }
    const writer = state.writer;
    if (writer && typeof writer.releaseLock === 'function') {
        try {
            writer.releaseLock();
        }
        catch {
            // ignore lock release errors
        }
    }
    state.reader = undefined;
    state.writer = undefined;
};
const flushBuffered = (state, emitter) => {
    if (state.paused)
        return;
    while (state.bufferedChunks.length > 0) {
        const chunk = state.bufferedChunks.shift();
        if (chunk !== undefined)
            emitter.emit('data', toBuffer(chunk));
    }
};
const startReading = (state, emitter) => {
    const token = state.socketToken;
    const readNext = async () => {
        if (state.socketToken !== token)
            return;
        if (!state.reader)
            return; //NOSONAR
        return state.reader
            .read()
            .then(async ({ done, value }) => {
            if (state.socketToken !== token)
                return;
            if (done) {
                emitter.emit('end');
                return undefined;
            }
            if (state.paused) {
                state.bufferedChunks.push(value);
            }
            else {
                emitter.emit('data', toBuffer(value));
            }
            return readNext();
        })
            .catch((error) => {
            emitter.emit('error', error);
        });
    };
    readNext().catch((error) => emitter.emit('error', error));
};
const bindSocketLifecycle = async (emitter, state, socket, emitConnect) => {
    state.socketToken += 1;
    const token = state.socketToken;
    state.socket = socket;
    state.closed = false;
    const waitForOpen = withTimeout(socket.opened, state.timeoutMs, 'Cloudflare socket connection timed out');
    return waitForOpen
        .then(() => {
        state.reader = socket.readable.getReader();
        state.writer = socket.writable.getWriter();
        if (emitConnect)
            emitter.emit('connect');
        startReading(state, emitter);
        socket.closed
            .then(() => {
            if (state.socketToken !== token)
                return;
            if (state.closed)
                return;
            state.closed = true;
            emitter.emit('close');
            try {
                releaseStreamLocks(state);
            }
            catch {
                // ignore release errors
            }
            emitter.removeAllListeners();
        })
            .catch((error) => {
            if (state.socketToken !== token)
                return;
            if (state.closed)
                return;
            state.closed = true;
            emitter.emit('error', error);
            try {
                releaseStreamLocks(state);
            }
            catch {
                // ignore release errors
            }
            emitter.removeAllListeners();
        });
    })
        .catch((error) => {
        emitter.emit('error', error);
        try {
            releaseStreamLocks(state);
        }
        catch {
            // ignore release errors
        }
        emitter.removeAllListeners();
    });
};
const createEmitterHandlers = (emitter, state) => {
    emitter.write = (data) => {
        if (!state.writer)
            return false;
        try {
            const payload = data instanceof Uint8Array ? data : new Uint8Array(data);
            const writePromise = state.writer.write(payload);
            writePromise.catch((error) => emitter.emit('error', error));
            if (state.writer.desiredSize !== null && state.writer.desiredSize <= 0) {
                state.writer.ready
                    .then(() => emitter.emit('drain'))
                    .catch((error) => emitter.emit('error', error));
                return false;
            }
            return true;
        }
        catch (error) {
            emitter.emit('error', error);
            return false;
        }
    };
    emitter.end = () => {
        const socket = state.socket;
        if (!socket)
            return;
        socket
            .close()
            .then(() => {
            emitter.emit('close');
            releaseStreamLocks(state);
            emitter.removeAllListeners();
        })
            .catch((error) => {
            emitter.emit('error', error);
            releaseStreamLocks(state);
            emitter.removeAllListeners();
        });
    };
    emitter.destroy = emitter.end;
    emitter.connect = (...args) => {
        const last = args.at(-1);
        if (typeof last === 'function') {
            emitter.once('connect', last);
        }
        return emitter;
    };
    emitter.startTls = async () => {
        const socket = state.socket;
        if (!socket || typeof socket.startTls !== 'function') {
            throw ErrorFactory.createConnectionError('Cloudflare socket does not support STARTTLS');
        }
        releaseStreamLocks(state);
        state.bufferedChunks = [];
        const tlsSocket = socket.startTls();
        await bindSocketLifecycle(emitter, state, tlsSocket, false);
    };
    emitter.pause = () => {
        state.paused = true;
    };
    emitter.resume = () => {
        state.paused = false;
        flushBuffered(state, emitter);
    };
    emitter.setTimeout = (timeoutMs, callback) => {
        state.timeoutToken += 1;
        const token = state.timeoutToken;
        const signal = createTimeoutSignal(timeoutMs);
        if (!signal)
            return;
        signal.addEventListener('abort', () => {
            if (state.timeoutToken !== token)
                return;
            emitter.emit('timeout');
            callback?.();
        }, { once: true });
    };
    emitter.setNoDelay = () => undefined;
    emitter.setKeepAlive = () => undefined;
    emitter.ref = () => undefined;
    emitter.unref = () => undefined;
};
function createCloudflareSocket(hostname, port, options = {}) {
    const emitter = new EventEmitter();
    const secureTransport = options.tls === true ? 'starttls' : 'off';
    const state = {
        paused: false,
        bufferedChunks: [],
        closed: false,
        timeoutToken: 0,
        socketToken: 0,
        timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
    };
    createEmitterHandlers(emitter, state);
    getCloudflareConnect()
        .then((connectFn) => connectFn({ hostname, port }, { secureTransport, allowHalfOpen: false }))
        .then(async (socket) => bindSocketLifecycle(emitter, state, socket, true))
        .catch((error) => emitter.emit('error', error));
    return emitter;
}
export const CloudflareSocket = Object.freeze({
    create: createCloudflareSocket,
});
