import { serveDir } from "@std/http/file-server";

const RELOAD_PATH = "/__reload";
const RELOAD_SCRIPT =
  `<script>new EventSource("${RELOAD_PATH}").addEventListener("reload",()=>location.reload())</script>`;
const DIST_FILE = "dist/dist.js";

const clients = new Set<ReadableStreamDefaultController>();

function broadcast(event: string, data = "") {
  const msg = new TextEncoder().encode(`event: ${event}\ndata: ${data}\n\n`);
  for (const c of clients) {
    try {
      c.enqueue(msg);
    } catch {
      clients.delete(c);
    }
  }
}

function sseHandler(req: Request): Response {
  const stream = new ReadableStream({
    start(controller) {
      clients.add(controller);
      req.signal.addEventListener("abort", () => clients.delete(controller));
    },
  });
  return new Response(stream, {
    headers: {
      "content-type": "text/event-stream",
      "cache-control": "no-cache",
    },
  });
}

const handler = async (req: Request): Promise<Response> => {
  const url = new URL(req.url);

  // SSE endpoint for live reload
  if (url.pathname === RELOAD_PATH) return sseHandler(req);

  // serve static files
  const res = await serveDir(req, { fsRoot: Deno.cwd() });

  // inject live-reload script into HTML responses
  if (res.headers.get("content-type")?.includes("text/html")) {
    const body = await res.text();
    const headers = new Headers(res.headers);
    headers.set("content-type", "text/html; charset=utf-8");
    headers.delete("content-length");
    return new Response(body.replace("</body>", RELOAD_SCRIPT + "</body>"), {
      status: res.status,
      headers,
    });
  }

  return res;
};

let reloadTimer: number | undefined;

async function startWatcher() {
  try {
    const watcher = Deno.watchFs(DIST_FILE);
    for await (const event of watcher) {
      if (event.kind === "modify" || event.kind === "create") {
        clearTimeout(reloadTimer);
        reloadTimer = setTimeout(() => broadcast("reload"), 200);
      }
    }
  } catch (err) {
    console.error("Watcher error:", err);
  }
}

function startBuildWatch() {
  const cmd = new Deno.Command("deno", {
    args: [
      "run",
      "-A",
      "npm:esbuild",
      "--bundle",
      "src/main.ts",
      `--outfile=${DIST_FILE}`,
      "--format=esm",
      "--watch=forever",
    ],
    stdout: "inherit",
    stderr: "inherit",
  });

  const proc = cmd.spawn();

  // clean up on exit
  globalThis.addEventListener("unload", () => {
    proc.kill();
  });

  proc.status.then((status) => {
    if (!status.success) {
      console.error("esbuild exited with code", status.code);
    }
  });

  return proc;
}

console.log("");
console.log("  ╔═══════════════════════════════╗");
console.log("  ║   deftoku dev server          ║");
console.log("  ║   http://localhost:8000       ║");
console.log("  ╚═══════════════════════════════╝");
console.log("");

startBuildWatch();
startWatcher();
Deno.serve({ port: 8000, hostname: "0.0.0.0", onListen: () => {} }, handler);
