/**
 * Bridge registry error-path tests for document extractor and renderer plugins.
 *
 * These tests cover the observable behaviour of register/unregister/clear at
 * the TypeScript/Node layer.  register_document_extractor and register_renderer
 * are generated by the alef trait-bridge codegen and present on the native
 * kreuzberg module at runtime; they do not appear in the curated index.d.ts
 * re-export because the TypeScript wrapper only surfaces a subset of the API.
 *
 * A DocumentExtractor bridge object must expose:
 *   name(): string
 *   extract_bytes(content, mimeType, configJson): string   (JSON InternalDocument)
 *   supported_mime_types(): string[]
 *
 * A Renderer bridge object must expose:
 *   name(): string
 *   render(docJson): string
 */

import { describe, it, expect } from "vitest";
import { extractBytesSync, listDocumentExtractors, listRenderers } from "kreuzberg";

// The register/unregister/clear functions are exported by the native module but
// not re-typed in the public TypeScript wrapper.  Import the native binding
// directly so we can reach the full API surface without 'any' sprawl.
import kreuzberg from "kreuzberg";
const native = kreuzberg as unknown as Record<string, (...args: unknown[]) => unknown>;

function registerDocumentExtractor(obj: unknown): void {
	(native["registerDocumentExtractor"] as (o: unknown) => void)(obj);
}
function unregisterDocumentExtractor(name: string): void {
	(native["unregisterDocumentExtractor"] as (n: string) => void)(name);
}
function clearDocumentExtractors(): void {
	(native["clearDocumentExtractors"] as () => void)();
}
function registerRenderer(obj: unknown): void {
	(native["registerRenderer"] as (o: unknown) => void)(obj);
}
function unregisterRenderer(name: string): void {
	(native["unregisterRenderer"] as (n: string) => void)(name);
}
function clearRenderers(): void {
	(native["clearRenderers"] as () => void)();
}

// ---------------------------------------------------------------------------
// Minimal stub factory helpers
// ---------------------------------------------------------------------------

function makeExtractor(name: string, mimeType = "application/x-test"): object {
	return {
		name: (): string => name,
		version: (): string => "0.0.1",
		initialize: (): void => {
			/* no-op */
		},
		shutdown: (): void => {
			/* no-op */
		},
		supported_mime_types: (): string[] => [mimeType],
		extract_bytes: (_content: Uint8Array, _mimeType: string, _configJson: string): string =>
			JSON.stringify({
				source_format: "plain",
				mime_type: "text/plain",
				elements: [],
				relationships: [],
				images: [],
				tables: [],
			}),
	};
}

function makeRenderer(name: string): object {
	return {
		name: (): string => name,
		version: (): string => "0.0.1",
		initialize: (): void => {
			/* no-op */
		},
		shutdown: (): void => {
			/* no-op */
		},
		render: (_docJson: string): string => "rendered",
	};
}

// ---------------------------------------------------------------------------
// DocumentExtractor tests
// ---------------------------------------------------------------------------

describe("plugins: document extractor registry", () => {
	it("register_duplicate_extractor_replaces: second registration silently replaces first", () => {
		const name = "_test_ts_dup_extractor";
		try {
			registerDocumentExtractor(makeExtractor(name, "application/x-ts-dup1"));
			registerDocumentExtractor(makeExtractor(name, "application/x-ts-dup2"));
			const listed = listDocumentExtractors();
			const count = listed.filter((n) => n === name).length;
			expect(count).toBe(1);
		} finally {
			unregisterDocumentExtractor(name);
		}
	});

	it("unregister_unknown_extractor_returns_ok: unregistering unknown name is a no-op", () => {
		// Must not throw
		expect(() => {
			unregisterDocumentExtractor("_test_ts_never_registered_extractor_xyz");
		}).not.toThrow();
	});

	it("clear_then_list_extractor_empty: list is empty after clear", () => {
		registerDocumentExtractor(makeExtractor("_test_ts_clear_a", "application/x-ts-clear-a"));
		registerDocumentExtractor(makeExtractor("_test_ts_clear_b", "application/x-ts-clear-b"));
		clearDocumentExtractors();
		const listed = listDocumentExtractors();
		expect(listed).toEqual([]);
	});

	it("extract_after_unregister_uses_builtin: built-in extractor is used after custom removed", () => {
		const name = "_test_ts_unreg_plain";
		registerDocumentExtractor(makeExtractor(name, "text/plain"));
		unregisterDocumentExtractor(name);

		// Must not throw; falls back to the built-in plain-text extractor.
		const encoded = new TextEncoder().encode("hello world");
		const result = extractBytesSync(encoded, "text/plain", undefined);
		expect(result).toBeDefined();
	});
});

// ---------------------------------------------------------------------------
// Renderer tests
// ---------------------------------------------------------------------------

describe("plugins: renderer registry", () => {
	it("register_duplicate_renderer_replaces: second registration silently replaces first", () => {
		const name = "_test_ts_dup_renderer";
		try {
			registerRenderer(makeRenderer(name));
			registerRenderer(makeRenderer(name));
			const listed = listRenderers();
			const count = listed.filter((n) => n === name).length;
			expect(count).toBe(1);
		} finally {
			unregisterRenderer(name);
		}
	});

	it("unregister_unknown_renderer_returns_ok: unregistering unknown name is a no-op", () => {
		expect(() => {
			unregisterRenderer("_test_ts_never_registered_renderer_xyz");
		}).not.toThrow();
	});

	it("clear_then_list_renderer_empty: list is empty after clear", () => {
		registerRenderer(makeRenderer("_test_ts_renderer_clear_a"));
		registerRenderer(makeRenderer("_test_ts_renderer_clear_b"));
		clearRenderers();
		const listed = listRenderers();
		expect(listed).toEqual([]);
	});

	it("list_renderers_after_unregister_does_not_include_removed: name absent after unregister", () => {
		const name = "_test_ts_unregister_renderer_check";
		registerRenderer(makeRenderer(name));
		expect(listRenderers()).toContain(name);
		unregisterRenderer(name);
		expect(listRenderers()).not.toContain(name);
	});
});
