import { parentPort, workerData } from "node:worker_threads";
import { messageHandler } from "./messageHandler.server.js";
import { register as registerTsx } from "tsx/esm/api";
import type { ReadyMessage } from "../types.js";
import { createLogger } from "vite";
import { handleError } from "../../error/handleError.js";
import { sendMessage } from "../sendMessage.js";
import { setMaxListenersOnPort, unrefPort } from "../../stream/setMaxListeners.js";
import { ModuleRunner, ESModulesEvaluator } from "vite/module-runner";
import { createRunnerTransport } from "./createRunnerTransport.js";
import { setRunner, setRpc } from "./runnerInstance.js";

// Initialize worker
if (!parentPort) {
  throw new Error("This module must be run as a worker");
}

const logger = createLogger(workerData.resolvedConfig?.logLevel ?? "info", {
  prefix: "rsc-worker",
});

// parentPort handles all messages, so needs a higher limit
if (parentPort) {
  setMaxListenersOnPort(parentPort, 500);
}

parentPort?.on("message", messageHandler);

try {
  const isBuildMode =
    workerData.configEnv?.command === "build" ||
    workerData.resolvedConfig?.mode === "production";

  if (workerData.verbose) {
    logger.info(
      isBuildMode
        ? "Build mode detected - files are already built, skipping tsx hook"
        : "Development/dev server mode detected"
    );
  }

  // Keep the tsx hook around for the rare non-runner import path where a
  // module that isn't transformed by Vite (e.g. a vendored dependency that
  // ships .ts sources) still needs TypeScript stripping. Runner-loaded
  // modules receive code that's already been through Vite's transform.
  registerTsx();

  parentPort!.on("messageerror", (error: Error) => {
    logger.error("Parent port message serialization failed.", { error });
    // Can't send via parentPort since that's what failed, so just log
  });

  // ModuleRunner-based fetch transport for project source. Replaces Node's
  // native import() so dev:ssr invalidates per-module instead of restarting
  // the worker on every save.
  if (!isBuildMode && workerData.runnerPort) {
    try {
      setMaxListenersOnPort(workerData.runnerPort, 500);
      unrefPort(workerData.runnerPort);
      workerData.runnerPort.start();
      const { transport, rpc } = createRunnerTransport(workerData.runnerPort);
      const runner = new ModuleRunner(
        {
          transport,
          hmr: false,
          sourcemapInterceptor: false,
        },
        new ESModulesEvaluator()
      );
      setRunner(runner);
      setRpc(rpc);
      if (workerData.verbose) {
        logger.info("ModuleRunner initialized");
      }
    } catch (err) {
      logger.error(`Failed to initialize ModuleRunner: ${String(err)}`);
    }
  }

  // Notify parent that we're ready
  parentPort!.postMessage({
    type: "READY",
    env: process.env["NODE_ENV"],
    pid: process.pid,
    id: "worker/rsc",
  } satisfies ReadyMessage);

  if (process.env["NODE_ENV"] === "production") {
    throw new Error("This module should not run in production mode.");
  }
} catch (error: unknown) {
  const handledError = handleError({
    error,
    logger,
    panicThreshold: workerData.userOptions.panicThreshold,
    context: "rsc-worker",
  });
  // In dev mode, try to send error message before exiting
  if (parentPort && handledError != null) {
    sendMessage(
      {
        type: "ERROR",
        id: "worker/rsc",
        error: handledError,
      },
      parentPort
    );
  }
}
