import { PassThrough } from 'node:stream';
import { createReadableStreamFromReadable } from '@react-router/node';
import { isbot } from 'isbot';
import * as React from 'react';
import type { RenderToPipeableStreamOptions } from 'react-dom/server';
import { renderToPipeableStream } from 'react-dom/server';
import type {
  // AppLoadContext,
  EntryContext,
} from 'react-router';
import { ServerRouter } from 'react-router';

const ABORT_DELAY = 5_000;

export default function handleRequest(
  request: Request,
  responseStatusCode: number,
  responseHeaders: Headers,
  routerContext: EntryContext
  // loadContext: AppLoadContext
): Promise<Response> {
  return new Promise<Response>((resolve, reject) => {
    let shellRendered = false;
    let statusCode = responseStatusCode;
    const userAgent = request.headers.get('user-agent');

    // Ensure requests from bots and SPA Mode renders wait for all content to load before responding
    // https://react.dev/reference/react-dom/server/renderToPipeableStream#waiting-for-all-content-to-load-for-crawlers-and-static-generation
    const readyOption: keyof RenderToPipeableStreamOptions =
      (userAgent && isbot(userAgent)) || routerContext.isSpaMode
        ? 'onAllReady'
        : 'onShellReady';
    let abortDelay: ReturnType<typeof setTimeout> | undefined;
    let ready = false;

    const { pipe, abort } = renderToPipeableStream(
      <ServerRouter context={routerContext} url={request.url} />,
      {
        [readyOption]() {
          ready = true;
          if (readyOption === 'onAllReady' && abortDelay) {
            clearTimeout(abortDelay);
          }
          shellRendered = true;
          const body = new PassThrough();
          const stream = createReadableStreamFromReadable(body);

          responseHeaders.set('Content-Type', 'text/html');

          resolve(
            new Response(stream, {
              headers: responseHeaders,
              status: statusCode,
            })
          );

          pipe(body);
        },
        onShellError(error: unknown) {
          reject(error);
        },
        onError(error: unknown) {
          statusCode = 500;
          if (shellRendered) {
            console.error(error);
          }
        },
      }
    );

    abortDelay = setTimeout(abort, ABORT_DELAY);
    if (readyOption === 'onAllReady' && ready) {
      clearTimeout(abortDelay);
    }
  });
}
