import {ServiceError} from "@grpc/grpc-js";
import {Access} from "../../access";
import {IronPdfServiceClient} from "../../generated_proto/ironpdfengineproto/IronPdfService";
import {EmptyResultP__Output} from "../../generated_proto/ironpdfengineproto/EmptyResultP";
import {PdfiumGetAnnotationsResultP__Output} from "../../generated_proto/ironpdfengineproto/PdfiumGetAnnotationsResultP";
import {PdfiumWrappedPdfAnnotationP__Output} from "../../generated_proto/ironpdfengineproto/PdfiumWrappedPdfAnnotationP";
import {BookmarkDestinations, LinkAnnotation} from "../../../public/annotation";
import {handleEmptyResultP__Output, handleRemoteException} from "../util";
 
/**
 * URL prefix used to encode an internal hyperlink (goto-page) target inside the
 * existing {@code Pdfium_Annotation_AddLinkAnnotation} RPC. Must match the
 * {@code InternalLinkPrefixes.InternalLinkPrefix} constant in the C#
 * {@code IronPdf.GrpcLayer.InternalPrefixes} / {@code IronPdfServiceHandler}.
 *
 * <p>The {@code x-} prefix follows RFC 6648 conventions for experimental /
 * private URL schemes and cannot collide with any real URL scheme a user would
 * construct.</p>
 */
const INTERNAL_LINK_PREFIX = "x-ironpdf-goto-page:";
 
/**
 * Retrieve all heterogeneous annotations (text, free-text, link, ...) on a single page
 * via the {@code Pdfium_Annotation_GetAnnotations} unary RPC.
 *
 * The returned wrapped annotations carry one of the proto sub-types ({@code text},
 * {@code freetext}, {@code link}). Callers inspect the {@code annotations} oneof to
 * dispatch by sub-type.
 */
export async function getAnnotations(
	id: string,
	pageIndex: number
): Promise<PdfiumWrappedPdfAnnotationP__Output[]> {
	const client: IronPdfServiceClient = await Access.ensureConnection();
 
	return new Promise(
		(
			resolve: (_: PdfiumWrappedPdfAnnotationP__Output[]) => void,
			reject: (errorMsg: string) => void
		) => {
			client.Pdfium_Annotation_GetAnnotationsRequestP(
				{
					document: {documentId: id},
					pageIndex: pageIndex,
				},
				(
					err: ServiceError | null,
					value: PdfiumGetAnnotationsResultP__Output | undefined
				) => {
					if (err) {
						reject(`${err.name}/n${err.message}`);
						return;
					}
					if (!value) {
						reject("No response from IronPdfEngine for getAnnotations");
						return;
					}
					if (value.exception) {
						handleRemoteException(value.exception, reject);
						return;
					}
					resolve(value.result?.annotations ?? []);
				}
			);
		}
	);
}
 
/**
 * Add an internal hyperlink annotation to the document. The link navigates to a
 * destination page within the same PDF when clicked.
 *
 * <p>Reuses the existing {@code Pdfium_Annotation_AddLinkAnnotation} RPC by encoding
 * the destination parameters into a special URL format:
 * {@code x-ironpdf-goto-page:{destPage},{destType},{left},{right},{top},{bottom},{zoom},{showBorder}}.
 * The engine detects this prefix and routes the request to its internal link handler.</p>
 *
 * <p>Mirrors {@code IronPdf.Engines.Pdfium.GrpcPdfClient.AddInternalLinkAnnotation}
 * on the C# side.</p>
 */
export async function addLinkAnnotation(
	id: string,
	link: LinkAnnotation
): Promise<void> {
	if (link.pageIndex < 0) {
		throw new Error("Invalid page index when adding link annotation");
	}
	if (link.destinationPageIndex < 0) {
		throw new Error("Invalid destination page index when adding link annotation");
	}
 
	const client: IronPdfServiceClient = await Access.ensureConnection();
 
	const destType: BookmarkDestinations = link.destinationType ?? BookmarkDestinations.Page;
	const destLeft = link.destinationLeft ?? 0;
	const destRight = link.destinationRight ?? 0;
	const destTop = link.destinationTop ?? 0;
	const destBottom = link.destinationBottom ?? 0;
	const destZoom = link.destinationZoom ?? 0;
	const showBorderFlag = link.showBorder ? 1 : 0;
 
	// Matches the 8-field encoding produced by C# AddInternalLinkAnnotation:
	//   {prefix}{destPageIndex},{destType},{destLeft},{destRight},{destTop},{destBottom},{destZoom},{showBorder}
	const encodedUrl =
		`${INTERNAL_LINK_PREFIX}${link.destinationPageIndex},${destType},` +
		`${destLeft},${destRight},${destTop},${destBottom},${destZoom},${showBorderFlag}`;
 
	// Color fallback: the engine parses `color_code` into IronSoftware.Drawing.Color and
	// throws on an empty string. Match the C# AddLinkAnnotation behavior by always
	// sending a valid color — default to black when the caller hasn't specified one.
	const colorCode = link.colorCode && link.colorCode.length > 0 ? link.colorCode : "#000000";
 
	return new Promise(
		(resolve: () => void, reject: (errorMsg: string) => void) => {
			client.Pdfium_Annotation_AddLinkAnnotation(
				{
					document: {documentId: id},
					name: link.title ?? "",
					url: encodedUrl,
					pageIndex: link.pageIndex,
					rectangle: {
						x: link.x,
						y: link.y,
						width: link.width,
						height: link.height,
					},
					colorCode: colorCode,
					Hidden: link.hidden ?? false,
				},
				(
					err: ServiceError | null,
					value: EmptyResultP__Output | undefined
				) => {
					if (err) {
						reject(`${err.name}/n${err.message}`);
						return;
					}
					if (!value) {
						reject("No response from IronPdfEngine for addLinkAnnotation");
						return;
					}
					handleEmptyResultP__Output(value, reject);
					resolve();
				}
			);
		}
	);
}
 
/**
 * Retrieve the number of annotations contained on the specified page via the
 * {@code Pdfium_Annotation_GetAnnotationCount} unary RPC.
 */
export async function getAnnotationCount(
	id: string,
	pageIndex: number
): Promise<number> {
	const client: IronPdfServiceClient = await Access.ensureConnection();
 
	return new Promise(
		(resolve: (_: number) => void, reject: (errorMsg: string) => void) => {
			client.Pdfium_Annotation_GetAnnotationCountRequestP(
				{
					document: {documentId: id},
					pageIndex: pageIndex,
				},
				(
					err: ServiceError | null,
					value:
						| import("../../generated_proto/ironpdfengineproto/IntResultP").IntResultP__Output
						| undefined
				) => {
					if (err) {
						reject(`${err.name}/n${err.message}`);
						return;
					}
					if (!value) {
						reject("No response from IronPdfEngine for getAnnotationCount");
						return;
					}
					if (value.exception) {
						handleRemoteException(value.exception, reject);
						return;
					}
					resolve(value.result ?? 0);
				}
			);
		}
	);
}