import assert from "node:assert/strict";
import { createRequire } from "node:module";
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 require = createRequire(import.meta.url);

// Load the native addon directly
const addonDir = path.resolve(
	__dirname,
	"..",
	"..",
	"..",
	"..",
	"rust-engine",
	"addon",
);
const platformTag = `${process.platform}-${process.arch}`;
const candidates = [
	path.join(addonDir, `forge_engine.${platformTag}.node`),
	path.join(addonDir, "forge_engine.dev.node"),
];

let native;
for (const candidate of candidates) {
	try {
		native = require(candidate);
		break;
	} catch {
		// try next
	}
}

if (!native) {
	console.error(
		"Native addon not found. Run `npm run build:native -w @singularity-forge/native` first.",
	);
	process.exit(1);
}

// ── normalizeForFuzzyMatch ──────────────────────────────────────────────

describe("normalizeForFuzzyMatch", () => {
	test("strips trailing whitespace per line", () => {
		assert.equal(
			native.normalizeForFuzzyMatch("hello   \nworld  "),
			"hello\nworld",
		);
	});

	test("normalizes smart quotes to ASCII", () => {
		assert.equal(
			native.normalizeForFuzzyMatch("\u201Chello\u201D \u2018world\u2019"),
			"\"hello\" 'world'",
		);
	});

	test("normalizes dashes to ASCII hyphen", () => {
		assert.equal(native.normalizeForFuzzyMatch("a\u2013b\u2014c"), "a-b-c");
	});

	test("normalizes special spaces to regular space", () => {
		assert.equal(native.normalizeForFuzzyMatch("a\u00A0b\u3000c"), "a b c");
	});

	test("handles empty string", () => {
		assert.equal(native.normalizeForFuzzyMatch(""), "");
	});

	test("preserves leading whitespace", () => {
		assert.equal(native.normalizeForFuzzyMatch("  hello  "), "  hello");
	});
});

// ── fuzzyFindText ───────────────────────────────────────────────────────

describe("fuzzyFindText", () => {
	test("finds exact match", () => {
		const result = native.fuzzyFindText("hello world", "world");
		assert.equal(result.found, true);
		assert.equal(result.index, 6);
		assert.equal(result.matchLength, 5);
		assert.equal(result.usedFuzzyMatch, false);
		assert.equal(result.contentForReplacement, "hello world");
	});

	test("finds fuzzy match with smart quotes", () => {
		const content = "let x = \u201Chello\u201D;";
		const oldText = 'let x = "hello";';
		const result = native.fuzzyFindText(content, oldText);
		assert.equal(result.found, true);
		assert.equal(result.usedFuzzyMatch, true);
	});

	test("returns not found for missing text", () => {
		const result = native.fuzzyFindText("hello world", "xyz");
		assert.equal(result.found, false);
		assert.equal(result.index, -1);
		assert.equal(result.matchLength, 0);
	});

	test("returns correct UTF-16 index for non-ASCII content", () => {
		// Emoji U+1F600 is 2 UTF-16 code units (surrogate pair), 4 UTF-8 bytes
		const content = "\u{1F600}hello";
		const result = native.fuzzyFindText(content, "hello");
		assert.equal(result.found, true);
		// Emoji is 2 UTF-16 code units, so "hello" starts at index 2
		assert.equal(result.index, 2);
		assert.equal(result.matchLength, 5);
	});

	test("index is compatible with JS substring()", () => {
		const content = "abc\u{1F600}def";
		const result = native.fuzzyFindText(content, "def");
		assert.equal(result.found, true);
		// "abc" = 3, emoji = 2 UTF-16 code units → index 5
		assert.equal(result.index, 5);
		// Verify substring works correctly with the returned index
		const extracted = result.contentForReplacement.substring(
			result.index,
			result.index + result.matchLength,
		);
		assert.equal(extracted, "def");
	});

	test("fuzzy match with trailing whitespace differences", () => {
		const content = "hello   \nworld  ";
		const oldText = "hello\nworld";
		const result = native.fuzzyFindText(content, oldText);
		assert.equal(result.found, true);
		assert.equal(result.usedFuzzyMatch, true);
	});
});

// ── generateDiff ────────────────────────────────────────────────────────

describe("generateDiff", () => {
	test("generates diff for a line change", () => {
		const old = "line1\nline2\nline3";
		const newText = "line1\nmodified\nline3";
		const result = native.generateDiff(old, newText);
		assert.ok(result.diff.includes("line2"));
		assert.ok(result.diff.includes("modified"));
		assert.ok(result.diff.includes("-"));
		assert.ok(result.diff.includes("+"));
		assert.notEqual(result.firstChangedLine, null);
	});

	test("generates diff for an addition", () => {
		const old = "line1\nline3";
		const newText = "line1\nline2\nline3";
		const result = native.generateDiff(old, newText);
		assert.ok(result.diff.includes("+"));
		assert.ok(result.diff.includes("line2"));
	});

	test("generates diff for a deletion", () => {
		const old = "line1\nline2\nline3";
		const newText = "line1\nline3";
		const result = native.generateDiff(old, newText);
		assert.ok(result.diff.includes("-"));
		assert.ok(result.diff.includes("line2"));
	});

	test("returns empty diff for identical content", () => {
		const result = native.generateDiff("same", "same");
		assert.equal(result.diff, "");
		// napi-rs maps Option::None to undefined (not null)
		assert.equal(result.firstChangedLine, undefined);
	});

	test("respects context lines parameter", () => {
		const lines = Array.from({ length: 20 }, (_, i) => `line${i + 1}`);
		const old = lines.join("\n");
		lines[10] = "modified";
		const newText = lines.join("\n");
		const result = native.generateDiff(old, newText, 2);
		assert.ok(result.diff.includes("..."));
	});

	test("default context is 4 lines", () => {
		const lines = Array.from({ length: 20 }, (_, i) => `line${i + 1}`);
		const old = lines.join("\n");
		lines[10] = "modified";
		const newText = lines.join("\n");
		const result = native.generateDiff(old, newText);
		// Should show 4 context lines before and after
		assert.ok(result.diff.length > 0);
	});
});
