import { ReadableStream as NodeReadableStream } from 'node:stream/web';
import * as Vue from 'vue';
import { pipeToWebWritable, renderToString } from 'vue/server-renderer';
import { isbot } from 'isbot';
import { createSsrStreamResponse, transformReadableStreamWithRouter, } from '@tanstack/router-core/ssr/server';
function prependDoctype(readable) {
    const encoder = new TextEncoder();
    let sentDoctype = false;
    let reader;
    const releaseReader = () => {
        try {
            reader?.releaseLock();
        }
        catch {
            // ignore
        }
        reader = undefined;
    };
    return new NodeReadableStream({
        start() {
            reader = readable.getReader();
        },
        async pull(controller) {
            if (!sentDoctype) {
                sentDoctype = true;
                controller.enqueue(encoder.encode('<!DOCTYPE html>'));
                return;
            }
            try {
                const { done, value } = await reader.read();
                if (done) {
                    controller.close();
                    releaseReader();
                    return;
                }
                controller.enqueue(value);
            }
            catch (err) {
                controller.error(err);
                releaseReader();
            }
        },
        async cancel(reason) {
            try {
                await reader?.cancel(reason);
            }
            catch {
                // ignore
            }
            releaseReader();
        },
    });
}
export const renderRouterToStream = async ({ request, router, responseHeaders, App, }) => {
    const app = Vue.createSSRApp(App, { router });
    if (isbot(request.headers.get('User-Agent'))) {
        try {
            let cleanupAbortListener;
            const abortPromise = new Promise((_, reject) => {
                if (request.signal.aborted) {
                    reject(request.signal.reason);
                    return;
                }
                const onRequestAbort = () => reject(request.signal.reason);
                request.signal.addEventListener('abort', onRequestAbort, { once: true });
                cleanupAbortListener = () => {
                    request.signal.removeEventListener('abort', onRequestAbort);
                };
            });
            let fullHtml = await Promise.race([
                renderToString(app),
                abortPromise,
            ]).finally(() => cleanupAbortListener?.());
            router.serverSsr.setRenderFinished();
            const injectedHtml = router.serverSsr.takeBufferedHtml();
            if (injectedHtml) {
                fullHtml = fullHtml.replace(`</body>`, () => `${injectedHtml}</body>`);
            }
            const htmlOpenIndex = fullHtml.indexOf('<html');
            const htmlCloseIndex = fullHtml.indexOf('</html>');
            if (htmlOpenIndex !== -1 && htmlCloseIndex !== -1) {
                fullHtml = fullHtml.slice(htmlOpenIndex, htmlCloseIndex + 7);
            }
            else if (htmlOpenIndex !== -1) {
                fullHtml = fullHtml.slice(htmlOpenIndex);
            }
            return new Response(`<!DOCTYPE html>${fullHtml}`, {
                status: router.stores.statusCode.get(),
                headers: responseHeaders,
            });
        }
        finally {
            router.serverSsr?.cleanup();
        }
    }
    const { writable, readable } = new TransformStream();
    const innerWriter = writable.getWriter();
    let writerDone = false;
    const releaseWriter = () => {
        try {
            innerWriter.releaseLock();
        }
        catch {
            // already released / errored
        }
    };
    const abortVuePipe = (reason) => {
        if (writerDone)
            return;
        writerDone = true;
        void innerWriter
            .abort(reason)
            .catch(() => { })
            .finally(releaseWriter);
    };
    const vueWritable = new WritableStream({
        write(chunk) {
            return innerWriter.write(chunk);
        },
        close() {
            writerDone = true;
            return innerWriter.close().finally(releaseWriter);
        },
        abort(reason) {
            writerDone = true;
            return innerWriter.abort(reason).finally(releaseWriter);
        },
    });
    // `pipeToWebWritable` returns void (see @vue/server-renderer). Pass a
    // proxy writable so request aborts can abort the real TransformStream
    // writer even while Vue holds a lock on the proxy writable.
    try {
        pipeToWebWritable(app, {}, vueWritable);
    }
    catch (err) {
        console.error('Error in Vue pipeToWebWritable:', err);
        // Setup failed before any pipe was wired; abort writable so the
        // readable side errors instead of hanging until the lifetime timeout.
        abortVuePipe(err);
    }
    if (request.signal.aborted) {
        abortVuePipe(request.signal.reason);
    }
    else {
        const onRequestAbort = () => abortVuePipe(request.signal.reason);
        request.signal.addEventListener('abort', onRequestAbort, { once: true });
        router.serverSsr?.onCleanup(() => {
            request.signal.removeEventListener('abort', onRequestAbort);
        });
    }
    const doctypedStream = prependDoctype(readable);
    const responseStream = transformReadableStreamWithRouter(router, doctypedStream, { onAbort: abortVuePipe });
    return createSsrStreamResponse(router, new Response(responseStream, {
        status: router.stores.statusCode.get(),
        headers: responseHeaders,
    }));
};
//# sourceMappingURL=renderRouterToStream.jsx.map