import { createConnection } from "node:net";
import { DEFAULT_MAX_FRAME_LENGTH } from "@earendil-works/pi-protocol";
const MAX_UNIX_SOCKET_PATH_BYTES = process.platform === "linux" ? 107 : 103;
/** Creates fresh Unix-domain socket transports for PiClient connection attempts in Node-compatible runtimes. */
export function createUnixTransportFactory(options) {
    if (options.path.length === 0)
        throw new TypeError("Unix transport path must not be empty");
    if (Buffer.byteLength(options.path) > MAX_UNIX_SOCKET_PATH_BYTES) {
        throw new TypeError(`Unix transport path is too long; maximum is ${MAX_UNIX_SOCKET_PATH_BYTES} UTF-8 bytes`);
    }
    const maxPendingBytes = options.maxPendingBytes ?? DEFAULT_MAX_FRAME_LENGTH * 4;
    if (!Number.isSafeInteger(maxPendingBytes) || maxPendingBytes <= 0) {
        throw new TypeError("Unix transport maxPendingBytes must be a positive safe integer");
    }
    if (process.platform === "win32")
        throw new Error("Unix transport is not supported on Windows");
    return (handlers) => connectUnixSocket(options.path, maxPendingBytes, handlers);
}
function connectUnixSocket(path, maxPendingBytes, handlers) {
    return new Promise((resolve, reject) => {
        const socket = createConnection(path);
        let connected = false;
        let terminal = false;
        const close = () => {
            if (terminal)
                return;
            terminal = true;
            socket.destroy();
            if (connected)
                handlers.onClose();
            else
                reject(new Error("Unix transport closed before connecting"));
        };
        socket.once("connect", () => {
            if (terminal)
                return;
            connected = true;
            resolve(new UnixByteTransport(socket, maxPendingBytes, () => {
                terminal = true;
            }));
        });
        socket.on("data", (chunk) => {
            if (!terminal)
                handlers.onData(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength));
        });
        socket.once("end", close);
        socket.once("close", close);
        socket.once("error", (error) => {
            if (terminal)
                return;
            terminal = true;
            socket.destroy();
            if (connected)
                handlers.onError(error);
            else
                reject(error);
        });
    });
}
class UnixByteTransport {
    #socket;
    #maxPendingBytes;
    #markLocalClose;
    #closed = false;
    #pendingBytes = 0;
    #writeTail = Promise.resolve();
    constructor(socket, maxPendingBytes, markLocalClose) {
        this.#socket = socket;
        this.#maxPendingBytes = maxPendingBytes;
        this.#markLocalClose = markLocalClose;
    }
    send(chunk) {
        if (!(chunk instanceof Uint8Array)) {
            return Promise.reject(new TypeError("Unix transport chunks must be Uint8Array"));
        }
        if (this.#closed)
            return Promise.reject(new Error("Unix transport is closed"));
        if (this.#pendingBytes + chunk.byteLength > this.#maxPendingBytes) {
            return Promise.reject(new Error("Unix transport exceeded its pending byte limit"));
        }
        this.#pendingBytes += chunk.byteLength;
        const bytes = chunk.slice();
        const write = this.#writeTail.then(() => this.#write(bytes));
        const tracked = write.finally(() => {
            this.#pendingBytes -= bytes.byteLength;
        });
        this.#writeTail = tracked.catch(() => { });
        return tracked;
    }
    close() {
        if (this.#closed)
            return;
        this.#closed = true;
        this.#markLocalClose();
        this.#socket.destroy();
    }
    #write(chunk) {
        if (this.#closed || !this.#socket.writable)
            return Promise.reject(new Error("Unix transport is closed"));
        return new Promise((resolve, reject) => {
            let callbackComplete = false;
            let drainComplete = false;
            let requiresDrain;
            let settled = false;
            const onDrain = () => {
                drainComplete = true;
                finish();
            };
            const cleanup = () => {
                this.#socket.off("drain", onDrain);
                this.#socket.off("close", onClose);
            };
            const fail = (error) => {
                if (settled)
                    return;
                settled = true;
                cleanup();
                reject(error);
            };
            const finish = () => {
                if (settled || !callbackComplete || requiresDrain === undefined)
                    return;
                if (requiresDrain && !drainComplete)
                    return;
                settled = true;
                cleanup();
                resolve();
            };
            const onClose = () => fail(new Error("Unix transport closed during write"));
            try {
                this.#socket.once("close", onClose);
                const accepted = this.#socket.write(chunk, (error) => {
                    if (error) {
                        fail(error);
                        return;
                    }
                    callbackComplete = true;
                    finish();
                });
                requiresDrain = !accepted;
                if (requiresDrain)
                    this.#socket.once("drain", onDrain);
                finish();
            }
            catch (error) {
                fail(error instanceof Error ? error : new Error(String(error)));
            }
        });
    }
}
//# sourceMappingURL=unix.js.map