/**
 * In-process file watcher (dev). Classifies each change and chooses the
 * cheapest update that still shows the latest page:
 *
 *   *.css / styles/   -> invalidate CSS cache, push { type: "css" }  (instant swap)
 *   anything else      -> invalidate changed modules, rescan routes, and ask
 *                         the connected browser to sync fresh HTML. The server
 *                         process and HMR socket stay alive.
 */

import { watch, type FSWatcher } from "node:fs";
import type { HmrHub } from "./hmr.ts";
import type { DevAssetServer } from "./assets.ts";

export interface WatchOptions {
  appDir: string;
  hub: HmrHub;
  assets: DevAssetServer;
  /** Called with changed app-relative files that need an in-process hot update. */
  onHotChange: (files: string[]) => void | Promise<void>;
}

function isIgnored(rel: string): boolean {
  return (
    rel.includes("node_modules/") ||
    rel.includes(".wrnexus/") ||
    rel.includes(".wrnexus-hmr-") ||
    rel.startsWith("dist/") ||
    rel.includes("/dist/")
  );
}

type Kind = "css" | "server";

function classify(rel: string): Kind {
  if (rel.endsWith(".css") || rel.startsWith("styles/") || rel.includes("/styles/")) return "css";
  return "server";
}

/** Returns the watcher so the running server can close it during shutdown. */
export function startWatcher(opts: WatchOptions): FSWatcher | undefined {
  const { appDir, hub, assets } = opts;
  const pending = new Set<Kind>();
  const pendingFiles = new Set<string>();
  let timer: ReturnType<typeof setTimeout> | null = null;

  const flush = (): void => {
    timer = null;
    if (pending.has("server")) {
      pending.clear();
      const files = [...pendingFiles];
      pendingFiles.clear();
      void Promise.resolve(opts.onHotChange(files)).catch((error) => {
        console.error("[wrnexus] hot update failed", error);
      });
      return;
    }
    if (pending.has("css")) {
      assets.invalidateCss();
      hub.css();
    }
    pending.clear();
    pendingFiles.clear();
  };

  try {
    return watch(appDir, { recursive: true }, (_event, filename) => {
      if (!filename) return;
      const rel = filename.toString().replace(/\\/g, "/");
      if (isIgnored(rel)) return;
      pendingFiles.add(rel);
      pending.add(classify(rel));
      if (timer) clearTimeout(timer);
      timer = setTimeout(flush, 200); // debounce editor write bursts
    });
  } catch (err) {
    console.warn("[wrnexus] file watching unavailable; HMR disabled", err);
    return undefined;
  }
}
