import { workerData } from "node:worker_threads";
import { join } from "node:path";
import { handleError } from "../../error/handleError.js";
import type { HandleHtmlRenderFn } from "./types.js";
import { assertNonReactServer } from "../../config/getCondition.js";

// Import React DOM Client for RSC stream processing
import { createFromNodeStream } from "../../stream/createFromNodeStream.client.js";

import { createModuleResolutionMetrics } from "../../metrics/createModuleResolutionMetrics.js";
import { ReactDOMServer } from "../../vendor/vendor.client.js";

assertNonReactServer();

/**
 * Handle the render of an HTML stream from RSC chunks, creates the stream once and pipes directly.
 *
 * This html render expects all components as a serialized rsc stream.
 *
 * It does not have to resolve components, it just renders the html.
 *
 * @param handlerOptions
 * @param handlers
 * @param logger
 */
export const handleHtmlRender: HandleHtmlRenderFn = function _handleHtmlRender(
  handlerOptions,
  handlers
) {
  const {
    route,
    id = route,
    rscStream, // Use the RSC stream id passed from the main thread
    moduleRootPath,
    moduleBaseURL,
    moduleBasePath,
    verbose,
    logger,
    projectRoot,
  } = handlerOptions;
  
  try {
    if (verbose) {
      logger.info(`[html-worker:${route}] Creating HTML stream (${id})`);
    }

    if (!rscStream) {
      throw new Error("RSC stream is required for HTML rendering");
    }

    // Convert RSC stream to React elements using ReactDOMClient.createFromNodeStream
    //
    // IMPORTANT: ReactDOMClient comes from react-server-dom-esm/client.node
    // We have reverse-engineered our own types for this (plugin/types/react-server-dom-esm.d.ts)
    // because there's no official @types package for react-server-dom-esm
    //
    // ACTUAL SIGNATURE FROM SOURCE CODE (patches/react-server-dom-esm+0.0.1.patch:9437):
    // exports.createFromNodeStream = function (stream, moduleRootPath, moduleBaseURL, options)
    //
    // This takes 4 parameters:
    // 1. stream: NodeJS.ReadableStream - the RSC stream
    // 2. moduleRootPath: string - the module root path for resolving client modules
    // 3. moduleBaseURL: string - the module base URL for resolving client modules
    // 4. options: object - optional configuration (encodeFormAction, nonce, etc.)

    // Construct the correct moduleRootPath using the projectRoot + moduleBasePath
    let resolvedModuleRootPath = moduleRootPath || "";

    if (typeof resolvedModuleRootPath !== "string") {
      throw new Error("moduleRootPath is required");
    } else if (!resolvedModuleRootPath.startsWith(projectRoot)) {
      resolvedModuleRootPath = join(projectRoot, resolvedModuleRootPath);
    }

    if (!resolvedModuleRootPath.endsWith(moduleBasePath)) {
      resolvedModuleRootPath = resolvedModuleRootPath + moduleBasePath;
    }
    if (moduleBasePath === "" && !resolvedModuleRootPath.endsWith("/")) {
      resolvedModuleRootPath = `${resolvedModuleRootPath}/`;
    }

    if (verbose) {
      logger.info(
        `[html-worker:${route}] Final resolvedModuleRootPath: ${resolvedModuleRootPath}`
      );
    }

    // Start measuring module resolution time
    const moduleResolutionStartTime = performance.now();

    // Note: Module resolution metric will be emitted in onAllReady callback

    if (verbose) {
      logger.info(
        `[html-worker:${route}] Starting HTML render for route: ${route}`
      );
    }

    if (!rscStream.readable) {
      throw new Error("RSC stream is not readable");
    }

    // Convert RSC stream to React elements using createFromNodeStream (like client-side)
    const result = createFromNodeStream({
      rscStream: rscStream,
      moduleRootPath: resolvedModuleRootPath,
      moduleBasePath: moduleBasePath,
      moduleBaseURL: moduleBaseURL,
      logger,
    });

    const mergedPipeableStreamOptions = {
      ...workerData.userOptions?.clientPipeableStreamOptions,
      ...handlerOptions.clientPipeableStreamOptions,
    };
    // Render React elements to HTML stream using ReactDOMServer.renderToPipeableStream
    if (verbose) {
      logger.info(
        `[html-worker:${route}] clientPipeableStreamOptions: ${JSON.stringify(
          mergedPipeableStreamOptions
        )}`
      );
    }

    // Create the stream once and pipe directly with onData
    const { pipe } = ReactDOMServer.renderToPipeableStream(result.children, {
      ...mergedPipeableStreamOptions,
      onShellReady() {
        if (verbose) {
          logger.info(
            `[html-worker:${route}] Shell ready, starting to pipe HTML`
          );
        }
        if(handlers.onShellReady) {
          handlers.onShellReady(route);
        }
        if(mergedPipeableStreamOptions?.onShellReady) {
          mergedPipeableStreamOptions.onShellReady();
        }
      },
      onAllReady() {
        if (verbose) {
          logger.info(
            `[html-worker:${route}] All ready, HTML rendering complete`
          );
        }

        // Calculate module resolution time
        const moduleResolutionTime =
          performance.now() - moduleResolutionStartTime;

        // Send metrics
        if (handlers.onMetrics) {
          const moduleResolutionMetric = createModuleResolutionMetrics({
            route,
            workerType: "html",
            resolutionTime: moduleResolutionTime,
            fromMainThread: false,
            fromRscWorker: false,
            fromHtmlWorker: true,
            description: `Module resolution for route ${route}`,
          });
          handlers.onMetrics(route, moduleResolutionMetric);
        }
        
        if(handlers.onAllReady) {
          handlers.onAllReady(route);
        }
        if(mergedPipeableStreamOptions?.onAllReady) {
          mergedPipeableStreamOptions.onAllReady();
        }
      },
      onError(error, errorInfo) {
        if (verbose) {
          logger.error(
            `[html-worker:${route}] React rendering error: ${error}`
          );
        }

        handlers.onError(route, error, errorInfo);
        if(handlers.onError) {
          handlers.onError(route, error, errorInfo);
        }
        if(mergedPipeableStreamOptions?.onError) {
          mergedPipeableStreamOptions.onError(error, errorInfo);
        }
      },
    });

    // Create a custom writable stream that pipes directly to onData
    const customWritable = {
      write(chunk: any, _encoding?: any, callback?: any) {
        handlers.onData(id, chunk);
        if (callback) callback();
      },
      end(chunk?: any, _encoding?: any, callback?: any) {
        if (chunk) {
          handlers.onData(id, chunk);
        }
        handlers.onEnd(id);
        if (callback) callback();
      },
      on() {}, // No-op for event listeners
      once() {}, // No-op for event listeners
      emit() { return false; }, // No-op for event emitters
    };

    // Pipe the React stream directly to our custom writable
    pipe(customWritable as any);

    // Set up RSC stream error handling
    rscStream.on("error", (error) => {
      if (verbose) {
        logger.error(`[html-worker:${route}] RSC stream error: ${error}`);
      }

      handlers.onError(id, error, {
        componentStack: undefined,
        digest: undefined,
      });
    });
  } catch (error) {
    if (verbose) {
      logger.error(
        `[html-worker:${route}] Error in handleHtmlRender: ${error}`
      );
    }

    const panicError = handleError({
      error: error,
      logger: logger,
      panicThreshold: workerData.userOptions?.panicThreshold,
      context: `HTML worker error for route ${route}`,
    });

    if (panicError != null) {
      handlers.onError(id, panicError, {
        componentStack: undefined,
        digest: undefined,
      });
    }

    throw error;
  }
};
