/**
 * Native addon loader.
 *
 * Locates and loads the compiled Rust N-API addon (`.node` file).
 * Resolution order:
 *   1. @singularity-forge/engine-{platform} npm optional dependency (production install)
 *   2. rust-engine/addon/forge_engine.{platform}.node (local release build)
 *   3. rust-engine/addon/forge_engine.dev.node (local debug build)
 */

import { createHash } from "node:crypto";
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import * as path from "node:path";

// __dirname and require are available in both execution contexts:
//   - CJS (production build via tsc): provided natively by Node
//   - ESM (CI test loader): injected by the dist-redirect.mjs preamble
const _dirname = __dirname;
const _require = require;

const addonDir = path.resolve(
	_dirname,
	"..",
	"..",
	"..",
	"rust-engine",
	"addon",
);
const platformTag = `${process.platform}-${process.arch}`;

/** Map Node.js platform/arch to the npm package suffix */
const platformPackageMap: Record<string, string> = {
	"linux-x64": "linux-x64-gnu",
	"linux-arm64": "linux-arm64-gnu",
	"win32-x64": "win32-x64-msvc",
};

let _loadedSuccessfully = false;
let _loadedFrom: string | null = null;
let _loadedSha256: string | null = null;

function sha256OfFile(filePath: string): string | null {
	try {
		return createHash("sha256").update(readFileSync(filePath)).digest("hex");
	} catch {
		return null;
	}
}

/**
 * Append a structured load record to .sf/runtime/native-engine-load.jsonl so
 * operators / sweepers can audit which binary each SF process loaded, detect
 * ABI mismatches across daemon↔worker boundaries (different sha256 for the
 * same platform means workers loaded a different .node file than the daemon),
 * and discover silent fallbacks. cwd-relative; tolerates non-writable cwd.
 */
function logLoadRecord(record: Record<string, unknown>): void {
	try {
		const runtimeDir = path.join(process.cwd(), ".sf", "runtime");
		if (!existsSync(runtimeDir)) {
			mkdirSync(runtimeDir, { recursive: true });
		}
		const line = `${JSON.stringify(record)}\n`;
		const logPath = path.join(runtimeDir, "native-engine-load.jsonl");
		writeFileSync(logPath, line, { flag: "a" });
	} catch {
		/* non-fatal: log-only path */
	}
}

function loadNative(): Record<string, unknown> {
	const errors: string[] = [];

	const recordSuccess = (sourcePath: string, source: string): void => {
		_loadedSuccessfully = true;
		_loadedFrom = sourcePath;
		// npm-package path may not resolve to a file we can hash; that's ok.
		_loadedSha256 = sha256OfFile(sourcePath);
		logLoadRecord({
			ts: new Date().toISOString(),
			pid: process.pid,
			platformTag,
			source,
			binaryPath: sourcePath,
			sha256: _loadedSha256,
			loaded: true,
		});
	};

	// 1. Try the platform-specific npm optional dependency
	const packageSuffix = platformPackageMap[platformTag];
	if (packageSuffix) {
		try {
			const pkg = `@singularity-forge/engine-${packageSuffix}`;
			const result = _require(pkg) as Record<string, unknown>;
			// Resolve the actual loaded file path for hashing + audit.
			let resolved: string | null = null;
			try {
				resolved = _require.resolve(pkg);
			} catch {
				/* resolution failed but require succeeded — unusual but tolerate */
			}
			recordSuccess(resolved ?? `npm:${pkg}`, "npm-optional-dependency");
			return result;
		} catch (err) {
			const message = err instanceof Error ? err.message : String(err);
			errors.push(`@singularity-forge/engine-${packageSuffix}: ${message}`);
		}
	}

	// 2. Try local release build (rust-engine/addon/forge_engine.{platform}.node)
	const releasePath = path.join(addonDir, `forge_engine.${platformTag}.node`);
	try {
		const result = _require(releasePath) as Record<string, unknown>;
		recordSuccess(releasePath, "rust-engine-release");
		return result;
	} catch (err) {
		const message = err instanceof Error ? err.message : String(err);
		errors.push(`${releasePath}: ${message}`);
	}

	// 3. Try local dev build (rust-engine/addon/forge_engine.dev.node)
	const devPath = path.join(addonDir, "forge_engine.dev.node");
	try {
		const result = _require(devPath) as Record<string, unknown>;
		recordSuccess(devPath, "rust-engine-dev");
		return result;
	} catch (err) {
		const message = err instanceof Error ? err.message : String(err);
		errors.push(`${devPath}: ${message}`);
	}

	const details = errors.map((e) => `  - ${e}`).join("\n");
	const supportedPlatforms = Object.keys(platformPackageMap);

	// All 3 paths failed — log structured + emit a LOUD banner.
	logLoadRecord({
		ts: new Date().toISOString(),
		pid: process.pid,
		platformTag,
		source: "fallback-proxy",
		binaryPath: null,
		sha256: null,
		loaded: false,
		errors,
	});
	process.stderr.write(
		[
			"",
			"==================================================================",
			`[forge] NATIVE ADDON UNAVAILABLE for ${platformTag}`,
			"==================================================================",
			"Every call into the native API will throw NativeUnavailableError.",
			"Consumers with JS fallbacks (parseRoadmap, parsePlan, fuzzyFind)",
			"will degrade gracefully; others (grep, ast, edit, image, …) fail.",
			"",
			"Resolution paths attempted:",
			details,
			"",
			"To fix on this machine:",
			"  1. Enter the nix dev shell (provides cargo + rustc):",
			"       nix develop",
			"  2. Build the native engine (~5-10 min):",
			"       node rust-engine/scripts/build.js --dev",
			"  3. Re-launch SF.",
			"",
			"Or install the platform package:",
			`  npm install @singularity-forge/engine-${packageSuffix ?? "<platform>"}`,
			"",
			`Supported native platforms: ${supportedPlatforms.join(", ")}`,
			"==================================================================",
			"",
		].join("\n"),
	);
	// Graceful fallback: on unsupported platforms (e.g., win32-arm64), return a
	// proxy that throws on individual function calls rather than crashing the
	// entire import chain at startup (#1223). Consumers with JS fallbacks
	// (parseRoadmap, parsePlan, fuzzyFind, etc.) catch these and degrade gracefully.
	return new Proxy({} as Record<string, unknown>, {
		get(_target, prop) {
			return (..._args: unknown[]) => {
				throw new Error(
					`NativeUnavailableError: function '${String(prop)}' is not available on ${platformTag}. ` +
						"Run `nix develop` then `node rust-engine/scripts/build.js --dev` to build, " +
						`or install @singularity-forge/engine-${packageSuffix ?? "<platform>"}.`,
				);
			};
		},
	});
}

/** Inspection helpers (consumers can introspect load state for diagnostics). */
export function isNativeLoaded(): boolean {
	return _loadedSuccessfully;
}
export function nativeBinaryPath(): string | null {
	return _loadedFrom;
}
export function nativeBinarySha256(): string | null {
	return _loadedSha256;
}

export const native = loadNative() as {
	search: (content: Buffer | Uint8Array, options: unknown) => unknown;
	grep: (options: unknown) => unknown;
	killTree: (pid: number, signal: number) => number;
	listDescendants: (pid: number) => number[];
	processGroupId: (pid: number) => number | null;
	killProcessGroup: (pgid: number, signal: number) => boolean;
	glob: (
		options: unknown,
		onMatch?: ((match: unknown) => void) | undefined | null,
	) => Promise<unknown>;
	invalidateFsScanCache: (path?: string) => void;
	highlightCode: (
		code: string,
		lang: string | null,
		colors: unknown,
	) => unknown;
	supportsLanguage: (lang: string) => unknown;
	getSupportedLanguages: () => unknown;
	copyToClipboard: (text: string) => void;
	readTextFromClipboard: () => string | null;
	readImageFromClipboard: () => Promise<unknown>;
	astGrep: (options: unknown) => unknown;
	astEdit: (options: unknown) => unknown;
	htmlToMarkdown: (html: string, options: unknown) => unknown;
	wrapTextWithAnsi: (
		text: string,
		width: number,
		tabWidth?: number,
	) => string[];
	truncateToWidth: (
		text: string,
		maxWidth: number,
		ellipsisKind: number,
		pad: boolean,
		tabWidth?: number,
	) => string;
	sliceWithWidth: (
		line: string,
		startCol: number,
		length: number,
		strict: boolean,
		tabWidth?: number,
	) => unknown;
	extractSegments: (
		line: string,
		beforeEnd: number,
		afterStart: number,
		afterLen: number,
		strictAfter: boolean,
		tabWidth?: number,
	) => unknown;
	sanitizeText: (text: string) => string;
	visibleWidth: (text: string, tabWidth?: number) => number;
	fuzzyFind: (options: unknown) => unknown;
	normalizeForFuzzyMatch: (text: string) => string;
	fuzzyFindText: (content: string, oldText: string) => unknown;
	generateDiff: (
		oldContent: string,
		newContent: string,
		contextLines?: number,
	) => unknown;
	applyEdits: (
		filePath: string,
		edits: unknown[],
		options?: unknown,
	) => unknown;
	applyWorkspaceEdit: (documentEdits: unknown[], options?: unknown) => unknown;
	replaceSymbol: (
		filePath: string,
		symbolName: string,
		newBody: string,
		options?: unknown,
	) => unknown;
	insertAroundSymbol: (
		filePath: string,
		symbolName: string,
		position: string,
		code: string,
		options?: unknown,
	) => unknown;
	watchTree: (
		root: string,
		options: unknown,
		onEvents: (events: unknown[]) => void,
	) => number;
	stopWatch: (handle: number) => boolean;
	NativeImage: unknown;
	ttsrCompileRules: (rules: unknown[]) => number;
	ttsrCheckBuffer: (handle: number, buffer: string) => string[];
	ttsrFreeRules: (handle: number) => void;
	processStreamChunk: (chunk: Buffer, state?: unknown) => unknown;
	stripAnsiNative: (text: string) => string;
	sanitizeBinaryOutputNative: (text: string) => string;
	parseFrontmatter: (content: string) => unknown;
	extractSection: (content: string, heading: string, level?: number) => unknown;
	extractAllSections: (content: string, level?: number) => string;
	batchParseSfFiles: (directory: string) => unknown;
	parseRoadmapFile: (content: string) => unknown;
	truncateTail: (text: string, maxBytes: number) => unknown;
	truncateHead: (text: string, maxBytes: number) => unknown;
	truncateOutput: (text: string, maxBytes: number, mode?: string) => unknown;
	parseJson: (text: string) => unknown;
	parsePartialJson: (text: string) => unknown;
	parseStreamingJson: (text: string) => unknown;
	xxHash32: (input: string, seed: number) => number;
};
