import assert from "node:assert/strict";
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { fileURLToPath } from "node:url";
import { describe, test } from "vitest";

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const { watchTree } = await import(
	path.resolve(__dirname, "../../dist/edit/index.js")
);

function makeTmpDir() {
	return fs.mkdtempSync(path.join(os.tmpdir(), "sf-watch-test-"));
}

function waitForEvents(root, options, predicate, timeoutMs = 3000) {
	return new Promise((resolve, reject) => {
		const watch = watchTree(root, options ?? null);
		const timer = setTimeout(() => {
			watch.stop();
			reject(
				new Error(
					`Timed out after ${timeoutMs}ms waiting for events in ${root}`,
				),
			);
		}, timeoutMs);

		watch.on("events", (events) => {
			if (predicate(events)) {
				clearTimeout(timer);
				watch.stop();
				resolve(events);
			}
		});
	});
}

describe("native edit: watchTree()", () => {
	test("detects file creation", async ({ onTestFinished }) => {
		const dir = makeTmpDir();
		onTestFinished(() => fs.rmSync(dir, { recursive: true, force: true }));

		const pending = waitForEvents(dir, { debounceMs: 50 }, (events) =>
			events.some((event) => event.path.endsWith("hello.txt")),
		);

		await new Promise((resolve) => setTimeout(resolve, 100));
		fs.writeFileSync(path.join(dir, "hello.txt"), "hi");

		const events = await pending;
		assert.ok(events.some((event) => event.path.endsWith("hello.txt")));
	});

	test("respects ignore patterns", async ({ onTestFinished }) => {
		const dir = makeTmpDir();
		onTestFinished(() => fs.rmSync(dir, { recursive: true, force: true }));

		const collected = [];
		const watch = watchTree(dir, { ignore: ["*.log"], debounceMs: 50 });
		watch.on("events", (events) => collected.push(...events));

		await new Promise((resolve) => setTimeout(resolve, 100));
		fs.writeFileSync(path.join(dir, "ignored.log"), "log data");
		fs.writeFileSync(path.join(dir, "kept.txt"), "text data");
		await new Promise((resolve) => setTimeout(resolve, 600));
		watch.stop();

		assert.equal(
			collected.filter((event) => event.path.endsWith("ignored.log")).length,
			0,
		);
		assert.ok(collected.some((event) => event.path.endsWith("kept.txt")));
	});

	test("stop ends the watch", async ({ onTestFinished }) => {
		const dir = makeTmpDir();
		onTestFinished(() => fs.rmSync(dir, { recursive: true, force: true }));

		const received = [];
		const watch = watchTree(dir, { debounceMs: 50 });
		watch.on("events", (events) => received.push(...events));

		await new Promise((resolve) => setTimeout(resolve, 100));
		fs.writeFileSync(path.join(dir, "before.txt"), "a");
		await new Promise((resolve) => setTimeout(resolve, 300));
		watch.stop();

		const countAfterStop = received.length;
		fs.writeFileSync(path.join(dir, "after.txt"), "b");
		await new Promise((resolve) => setTimeout(resolve, 300));

		assert.equal(received.length, countAfterStop);
	});
});
