import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
import { getPresentUsers } from "@kentcdodds/workshop-presence/presence.server";
import {
  getApps,
  getWorkshopRoot
} from "@kentcdodds/workshop-utils/apps.server";
import { getWatcher } from "@kentcdodds/workshop-utils/change-tracker.server";
import { checkForUpdates } from "@kentcdodds/workshop-utils/git.server";
import { createRequestHandler } from "@remix-run/express";
import { installGlobals } from "@remix-run/node";
import { ip as ipAddress } from "address";
import chalk from "chalk";
import closeWithGrace from "close-with-grace";
import compression from "compression";
import express from "express";
import getPort, { portNumbers } from "get-port";
import morgan from "morgan";
import sourceMapSupport from "source-map-support";
import { WebSocket, WebSocketServer } from "ws";
installGlobals();
sourceMapSupport.install();
const viteDevServer = process.env.NODE_ENV === "production" ? null : await import("vite").then(
  (vite) => vite.createServer({
    server: { middlewareMode: true }
  })
);
const isProd = process.env.NODE_ENV === "production";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const isPublished = !fs.existsSync(path.join(__dirname, "..", "app"));
const isDeployed = process.env.KCDSHOP_DEPLOYED === "true" || process.env.KCDSHOP_DEPLOYED === "1";
const isRunningInBuildDir = path.dirname(__dirname).endsWith("dist");
const kcdshopAppRootDir = isRunningInBuildDir ? path.join(__dirname, "..", "..") : path.join(__dirname, "..");
const hasUpdatesPromise = checkForUpdates();
void getApps();
void getPresentUsers();
const workshopRoot = getWorkshopRoot();
const app = express();
app.use(compression());
app.disable("x-powered-by");
if (viteDevServer) {
  app.use(viteDevServer.middlewares);
} else {
  app.use(
    express.static(path.join(kcdshopAppRootDir, "build/client"), {
      maxAge: isProd ? "1h" : 0
    })
  );
}
app.use(
  express.static(path.join(workshopRoot, "public"), {
    maxAge: isProd ? "1h" : 0
  })
);
if (process.env.NODE_ENV !== "production" && !isPublished || isDeployed) {
  morgan.token("url", (req) => decodeURIComponent(req.url ?? ""));
  app.use(morgan("tiny"));
}
function getNumberOrNull(value) {
  if (value == null)
    return null;
  const number = Number(value);
  return Number.isNaN(number) ? null : number;
}
app.use((req, res, next) => {
  const segments = req.url.split("/").map((s) => s.trim()).filter(Boolean);
  let [first, second, ...rest] = segments;
  const firstNumber = getNumberOrNull(first);
  const secondNumber = getNumberOrNull(second);
  if (firstNumber === null && secondNumber === null)
    return next();
  if (firstNumber != null)
    first = firstNumber.toString().padStart(2, "0");
  if (secondNumber != null)
    second = secondNumber.toString().padStart(2, "0");
  const updatedUrl = `/${[first, second, ...rest].filter(Boolean).join("/")}`;
  if (req.url !== updatedUrl) {
    return res.redirect(302, updatedUrl);
  }
  next();
});
app.all(
  "*",
  createRequestHandler({
    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
    build: viteDevServer ? () => viteDevServer.ssrLoadModule("virtual:remix/server-build") : (
      // @ts-ignore (this may or may not be built at this time, but it will be in prod)
      await import("#build/server/index.js")
    )
  })
);
const desiredPort = Number(process.env.PORT || 5639);
const portToUse = await getPort({
  port: portNumbers(desiredPort, desiredPort + 100)
});
const server = app.listen(portToUse, async () => {
  const addy = server.address();
  const portUsed = desiredPort === portToUse ? desiredPort : addy && typeof addy === "object" ? addy.port : 0;
  if (portUsed !== desiredPort) {
    console.warn(
      chalk.yellow(
        `\u26A0\uFE0F  Port ${desiredPort} is not available, using ${portUsed} instead.`
      )
    );
  }
  console.log(`\u{1F428}  Let's get learning!`);
  const localUrl = `http://localhost:${portUsed}`;
  let lanUrl = null;
  const localIp = ipAddress() ?? "Unknown";
  if (/^10[.]|^172[.](1[6-9]|2[0-9]|3[0-1])[.]|^192[.]168[.]/.test(localIp)) {
    lanUrl = `http://${localIp}:${portUsed}`;
  }
  console.log(
    `
${chalk.bold("Local:")}            ${chalk.cyan(localUrl)}
${lanUrl ? `${chalk.bold("On Your Network:")}  ${chalk.cyan(lanUrl)}` : ""}
${chalk.bold("Press Ctrl+C to stop")}
	`.trim()
  );
  const hasUpdates = await hasUpdatesPromise;
  if (hasUpdates.updatesAvailable) {
    const updateCommand = chalk.blue.bold.bgWhite(" npx kcdshop update ");
    const updateLink = chalk.blue.bgWhite(` ${hasUpdates.diffLink} `);
    console.log(
      "\n",
      `\u{1F389}  There are ${chalk.yellow(
        "updates available"
      )} for this workshop repository.  \u{1F389}

To get the updates, stop the server and run the following command:

  ${updateCommand}

To view a diff, check:
  ${updateLink}`
    );
  }
});
const wss = new WebSocketServer({ server, path: "/__ws" });
let timer = null;
let fileChanges = /* @__PURE__ */ new Set();
getWatcher()?.on("all", (event, filePath) => {
  fileChanges.add(filePath);
  if (!timer) {
    timer = setTimeout(() => {
      if (fileChanges.size === 0)
        return;
      for (const client of wss.clients) {
        if (client.readyState === WebSocket.OPEN) {
          client.send(
            JSON.stringify({
              type: "kcdshop:file-change",
              data: { event, filePaths: Array.from(fileChanges) }
            })
          );
        }
      }
      fileChanges = /* @__PURE__ */ new Set();
      timer = null;
    }, 50);
  }
});
closeWithGrace(() => {
  return Promise.all([
    new Promise((resolve, reject) => {
      server.close((e) => e ? reject(e) : resolve("ok"));
    }),
    new Promise((resolve, reject) => {
      wss.close((e) => e ? reject(e) : resolve("ok"));
    })
  ]);
});
