import {Buffer} from "buffer";
import {
	BarcodeStampOptions,
	BarcodeType, ChangeTrackingModes,
	ChromePdfRenderOptions,
	DigitalSignature,
	VerifiedSignature,
	HtmlAffix,
	HtmlFilePath,
	HtmlStampOptions,
	HtmlString,
	HttpLoginCredentials,
	ImageBuffer,
	ImageFilePath,
	ImageStampOptions,
	ImageToPdfOptions,
	ImageType,
	LinearizationMode,
	PageInfo,
	PageRotation,
	PdfInput,
	PdfPageSelection,
	PdfPaperSize,
	PdfPassword, PdfPermission,
	RenderedElementLocation,
	SaveOptions,
	TextAffix,
	TextStampOptions,
} from "./types";

import {
	barcodeTypeSchema, booleanSchema, bufferArraySchema, bufferSchema, changeTrackingModesSchema,
	filePathSchema,
	htmlFilePathSchema,
	htmlStringSchema,
	imageBufferSchema,
	imageFilePathSchema,
	mapStringSchema,
	numberSchema, pdfFilePathSchema,
	pdfInputSchema,
	pdfPageSelectionSchema, pdfPasswordSchema, saveOptionsSchema, stringArraySchema,
	stringSchema, urlSchema
} from "../internal/zod/typeSchema";
import {z} from "zod";
import {pdfPermissionSchema} from "../internal/zod/securitySchema";
import {pdfDocumentSchema} from "../internal/zod/pdfDocumentSchema";
import {
	barcodeStampOptionsSchema,
	htmlStampOptionsSchema,
	imageStampOptionsSchema,
	textStampOptionsSchema
} from "../internal/zod/stampSchema";
import {htmlAffixSchema, textAffixSchema} from "../internal/zod/affixSchema";
import {digitalSignatureSchema} from "../internal/zod/signatureSchema";
import {imageToPdfOptionsSchema, imageTypeSchema} from "../internal/zod/imageSchema";
import {pdfPaperSizeSchema} from "../internal/zod/paperSchema";
import {pageRotationSchema} from "../internal/zod/pageSchema";
import {chromePdfRenderOptionsSchema, httpLoginCredentialsSchema} from "../internal/zod/renderSchema";
import {
	getFileName,
	getImageExtType,
	separateImageBufferOrImagePathInput,
	separatePdfInput
} from "../internal/grpc_layer/util";
import fs from "fs";
import {mergePdfs, renderHtmlToPdf, renderHtmlZipToPdf, renderUrlToPdf, renderHtmlFileToPdf} from "../internal/grpc_layer/chrome/render";
import {disposePdf, getBinaryData, openPdfFileBuffer} from "../internal/grpc_layer/pdfium/io";
import {Access} from "../internal/access";
import {renderImagesBufferToPdf, renderImagesFilesToPdf} from "../internal/grpc_layer/chrome/image";
import {compressAndSaveAs, compressImage, compressInMemory, compressInMemoryStream, compressStructTree} from "../internal/grpc_layer/pdfium/compress";
import {
	isLinearizedFromBytes,
	linearizeCoreFromBytes,
	linearizeCoreFromId,
	linearizeInMemoryFromIdStream
} from "../internal/grpc_layer/pdfium/linearize";
import {addLinkAnnotation, getAnnotationCount, getAnnotations} from "../internal/grpc_layer/pdfium/annotations";
import {LinkAnnotation} from "./annotation";
import {getBookmarks} from "../internal/grpc_layer/pdfium/bookmarks";
import {Bookmark} from "./bookmark";
import {Readable} from "stream";
import {
	duplicate,
	getPageInfo,
	insertPdf,
	removePage,
	resizePage,
	setPageRotation
} from "../internal/grpc_layer/pdfium/page";
import {extractRawImages, rasterizeToImageBuffers} from "../internal/grpc_layer/pdfium/image";
import Jimp from "jimp";
import {extractAllText, replaceText} from "../internal/grpc_layer/pdfium/text";
import {PdfAVersions, PdfUAVersions, toPdfA, toPdfUA, toPdfUAForScreenReader} from "../internal/grpc_layer/pdfium/pdfa";
import {getMetadataDict, removeMetadata, setMetadata, setMetadataDict} from "../internal/grpc_layer/pdfium/metadata";
import {getSignatureCount, getVerifiedSignatures, signPdf} from "../internal/grpc_layer/pdfium/signing";
import {addHtmlAffix, addTextAffix} from "../internal/grpc_layer/pdfium/headerFooter";
import {stampBarcode, stampHtml, stampImage, stampText} from "../internal/grpc_layer/chrome/stamp";
import {addBackgroundForeground} from "../internal/grpc_layer/pdfium/BackgroundForeground";
import {
	getPermission,
	removePasswordsAndEncryption,
	setOwnerPasswords, setSecurity,
	setUserPasswords
} from "../internal/grpc_layer/pdfium/security";
import { NaturalLanguages } from "./naturalLanguages";

/**
 * Represents a PDF document. Allows: loading, editing, manipulating, merging, signing printing and saving PDFs.
 *
 * @remark Make sure that you call {@link PdfDocument.close} or {@link cleanUp} to free the memory, when you stop using the PdfDocument object.
 */
export class PdfDocument{

	//#region io

	/**
	 * Open or Create a PdfDocument from a {@link PdfInput}
	 * @param pdfInput {@link PdfInput}
	 * @param options including {@link PdfPassword} {@link ChromePdfRenderOptions} {@link HttpLoginCredentials} mainHtmlFile
	 */
	public static async open(
		pdfInput: PdfInput,
		options?: {
			/**
			 * required for open a protected PDF file
			 * @default undefined
			 */
			password?: PdfPassword | undefined;
			/**
			 * Apply renderOptions if PdfInput is a {@link HtmlString} or {@link HtmlFilePath} or {@link ZipFilePath} or {@link Url}}
			 * @default undefined
			 */
			renderOptions?: ChromePdfRenderOptions | undefined;
			/**
			 * Apply httpLoginCredentials if PdfInput is a {@link HtmlString} or {@link HtmlFilePath} or {@link ZipFilePath} or {@link Url}}
			 * @default undefined
			 */
			httpLoginCredentials?: HttpLoginCredentials | undefined;
			/**
			 * Apply mainHtmlFile if PdfInput is {@link ZipFilePath}
			 * @default index.html
			 */
			mainHtmlFile?: string | undefined;
			/**
			 * Optionally track changes to the document (for use with incremental saves)
			 * @default {@link ChangeTrackingModes.AutoChangeTracking}
			 */
			trackChanges?: ChangeTrackingModes | undefined;
			// /**
			//  * Apply baseUrl if
			//  * The HTML base URL for which references to external CSS, Javascript and Image files will be relative.
			//  * @default undefined
			//  */
			// baseUrl ?: string | undefined;  //not supported
		} | undefined
	): Promise<PdfDocument> {
		return z.function()
			.args(
				pdfInputSchema,
				z.object({
					password: pdfPasswordSchema.optional(),
					renderOptions: chromePdfRenderOptionsSchema.optional(),
					httpLoginCredentials: httpLoginCredentialsSchema.optional(),
					mainHtmlFile: stringSchema.optional(),
					trackChanges: changeTrackingModesSchema.optional(),
				}).optional()
			)
			.returns(z.promise(pdfDocumentSchema))
			.implement(this.internal_open.bind(this))
			(pdfInput, options);
	}

	/**
	 * Open a PdfDocument from .pdf file
	 * @param pdfFilePath A path to .pdf file
	 * @param Optionally track changes to the document (for use with incremental saves)
	 */
	public static async fromFile(
		pdfFilePath: string, trackChanges?: ChangeTrackingModes | undefined
	): Promise<PdfDocument> {
		return z.function()
			.args(
				pdfFilePathSchema,
				changeTrackingModesSchema.optional()
			)
			.returns(z.promise(pdfDocumentSchema))
			.implement(this.internal_fromFile.bind(this))
			(pdfFilePath, trackChanges);
	}

	/**
	 * Create a PdfDocument from an Url
	 * @param url A website Url
	 * @param options including {@link ChromePdfRenderOptions}
	 */
	public static async fromUrl(
		url: URL | string,
		options?: {
			/**
			 * Apply renderOptions if PdfInput is a {@link HtmlString} or {@link HtmlFilePath} or {@link ZipFilePath} or {@link Url}}
			 * @default undefined
			 */
			renderOptions?: ChromePdfRenderOptions | undefined;
		} | undefined
	): Promise<PdfDocument> {
		return z.function()
			.args(
				z.union([urlSchema, stringSchema]),
				z.object({
					renderOptions: chromePdfRenderOptionsSchema.optional(),
				}).optional()
			)
			.returns(z.promise(pdfDocumentSchema))
			.implement(this.internal_fromUrl.bind(this))
			(url, options);
	}

	/**
	 * Creates a PDF file from a local Zip file, and returns it as a {@link PdfDocument}.
	 * IronPDF is a W3C standards compliant HTML rendering based on Google's Chromium browser.
	 * If your output PDF does not look as expected:
	 *
	 * - Validate your HTML file using  https://validator.w3.org/ &amp; CSS https://jigsaw.w3.org/css-validator/
	 *
	 * - To debug HTML, view the file in Chrome web browser's print preview which will work almost exactly as IronPDF.
	 *
	 * - Read our detailed documentation on pixel perfect HTML to PDF: https://ironpdf.com/tutorials/pixel-perfect-html-to-pdf/
	 *
	 * @param zipFilePath Path to a Zip to be rendered as a PDF.
	 * @param options including {@link ChromePdfRenderOptions} and `mainHtmlFile` a main .html file default: `index.html`
	 */
	public static async fromZip(
		zipFilePath: string,
		options?: {
			/**
			 * Apply renderOptions if PdfInput is a {@link HtmlString} or {@link HtmlFilePath} or {@link ZipFilePath} or {@link Url}}
			 * @default undefined
			 */
			renderOptions?: ChromePdfRenderOptions | undefined;
			/**
			 * a main .html file default: `index.html`
			 */
			mainHtmlFile?: string | undefined;
		} | undefined
	): Promise<PdfDocument> {
		return z.function()
			.args(
				stringSchema,
				z.object({
					renderOptions: chromePdfRenderOptionsSchema.optional(),
					mainHtmlFile: stringSchema.optional()
				}).optional()
			)
			.returns(z.promise(pdfDocumentSchema))
			.implement(this.internal_fromZip.bind(this))
			(zipFilePath, options);
	}

	/**
	 * Creates a PDF file from a Html string, and returns it as an {@link PdfDocument} object which can be edited and saved to disk or served on a website.
	 *
	 * ------------------------------------------------
	 *
	 * **Usage:**
	 * ```ts
	 * const pdf = await fromHtml(htmlStringOrHtmlFilePath, renderOptions);
	 * ```
	 *
	 * ------------------------------------------------
	 *
	 * @param htmlStringOrHtmlFilePath The Html to be rendered as a PDF.
	 * @param options including {@link ChromePdfRenderOptions}
	 * @returns             A `PdfDocument` generated from the provided HTML file.
	 *
	 * ---
	 * ### Important Notes:
	 *
	 * 🐳 **Docker Limitation:**
	 * This method **does not work** for **HTML file path** when the application runs inside a Docker environment
	 * due to rendering engine restrictions.
	 * In such cases, use `fromZip()` instead.
	 *
	 * 📄 **Input:** Requires access to a local HTML file on disk if htmlFilePath is passing.
	 *
	 * ---
	 * ### Related Methods:
	 * 📌 `fromZip()` — Recommended alternative for rendering from HTML file when running inside Docker.
	 */
	public static async fromHtml(
		htmlStringOrHtmlFilePath: string,
		options?: {
			/**
			 * Apply renderOptions if PdfInput is a {@link HtmlString} or {@link HtmlFilePath} or {@link ZipFilePath} or {@link Url}}
			 * @default undefined
			 */
			renderOptions?: ChromePdfRenderOptions | undefined;
		} | undefined
	): Promise<PdfDocument> {
		return z.function()
			.args(
				stringSchema,
				z.object({
					renderOptions: chromePdfRenderOptionsSchema.optional(),
				}).optional()
			)
			.returns(z.promise(pdfDocumentSchema))
			.implement(this.internal_fromHtml.bind(this))
			(htmlStringOrHtmlFilePath, options);
	}

	/**
	 *  Converts multiple image files to a PDF document.  Each image creates 1 page which matches the image
	 *  dimensions. The default PaperSize is A4. You can set it via ImageToPdfConverter.PaperSize.
	 *  Note: Imaging.ImageBehavior.CropPage will set PaperSize equal to ImageSize.
	 * @param images The image file path name(s) or {@link ImageBuffer} object(s)
	 * @param options including {@link ImageToPdfOptions}
	 */
	public static async fromImage(
		images: ImageFilePath | ImageFilePath[] | ImageBuffer | ImageBuffer[],
		options?: {
			/**
			 * Apply renderOptions if PdfInput is a {@link HtmlString} or {@link HtmlFilePath} or {@link ZipFilePath} or {@link Url}}
			 * @default undefined
			 */
			imageToPdfOptions?: ImageToPdfOptions | undefined;
		}
	): Promise<PdfDocument> {
		return z.function()
			.args(
				z.union([imageFilePathSchema, z.array(imageFilePathSchema), imageBufferSchema, z.array(imageBufferSchema)]),
				z.object({
					imageToPdfOptions: imageToPdfOptionsSchema.optional(),
				}).optional()
			)
			.returns(z.promise(pdfDocumentSchema))
			.implement(this.internal_fromImage.bind(this))
			(images, options);
	}

	/**
	 * Static method that joins (concatenates) multiple PDF documents together into one compiled PDF document.
	 * If the PDF contains form fields the form field in the resulting PDF's name will be appended with '_{index}' e.g. 'Name' will be 'Name_0'
	 * @param pdfs array of PDF
	 */
	public static async mergePdf(pdfs: PdfInput[]): Promise<PdfDocument> {
		return z.function()
			.args(
				z.array(pdfInputSchema)
			)
			.returns(z.promise(pdfDocumentSchema))
			.implement(this.internal_mergePdf.bind(this))
			(pdfs);
	}

	/**
	 * Saves the PdfDocument to a file.
	 * @param filePath Target file path
	 * @param saveOptions see {@link SaveOptions}
	 */
	public saveAs(filePath: string, saveOptions?: SaveOptions | undefined): Promise<void> {
		return z.function()
			.args(
				stringSchema,
				saveOptionsSchema.optional()
			)
			.returns(z.promise(z.void()))
			.implement(this.internal_saveAs.bind(this))
			(filePath, saveOptions);
	}

	/**
	 * Saves the PdfDocument to a binary (Buffer)
	 * @param saveOptions see {@link SaveOptions}
	 */
	public async saveAsBuffer(saveOptions?: SaveOptions | undefined): Promise<Buffer> {
		return z.function()
			.args(
				saveOptionsSchema.optional()
			)
			.returns(z.promise(bufferSchema))
			.implement(this.internal_saveAsBuffer.bind(this))
			(saveOptions);
	}

	//#endregion

	//#region compress
	/**
	 * Compress existing PDF images using JPG encoding and the specified settings.
	 * @deprecated Use {@link compressAndSaveAs}, {@link compressPdfToBytes}, or {@link compressPdfToStream} instead for better compression results.
	 * @param imageQuality Quality (1 - 100) to use during compression
	 * @param scaleToVisibleSize Scale down the image resolution according to its visible size in the PDF document
	 */
	public async compressSize(
		imageQuality: number,
		scaleToVisibleSize = false
	): Promise<void> {
		return await z.function()
			.args(
				numberSchema,
				booleanSchema.optional()
			)
			.returns(z.promise(z.void()))
			.implement(this.internal_compressSize.bind(this))
			(imageQuality, scaleToVisibleSize);
	}

	/**
	 *  Remove document struct tree information which describes the logical layout of the document.
	 *  Removing the "structure tree" can significantly reduce the disk space used by the document.
	 *  Removing the "structure tree" of a complicated document can negatively impact text selection.
	 */
	public async compressStructTree(): Promise<void> {
		return this.internal_compressStructTree()
	}

	/**
	 * Compress the PDF and return the result as a Buffer (byte array).
	 * Uses QPdf compression which can significantly reduce file size.
	 * @param imageQuality Optional JPEG quality (1-100) for image compression. If omitted, default compression is applied.
	 * @returns A Promise resolving to a Buffer containing the compressed PDF bytes.
	 */
	public async compressPdfToBytes(
		imageQuality?: number
	): Promise<Buffer> {
		return await z.function()
			.args(
				numberSchema.optional()
			)
			.returns(z.promise(z.instanceof(Buffer)))
			.implement(this.internal_compressPdfToBytes.bind(this))
			(imageQuality);
	}

	/**
	 * Compress the PDF and return the result as a Readable stream.
	 * Uses QPdf compression which can significantly reduce file size.
	 * Useful for piping directly to file streams or HTTP responses without buffering the entire PDF in memory.
	 * @param imageQuality Optional JPEG quality (1-100) for image compression. If omitted, default compression is applied.
	 * @returns A Promise resolving to a Readable stream of the compressed PDF bytes.
	 */
	public async compressPdfToStream(
		imageQuality?: number
	): Promise<Readable> {
		return await z.function()
			.args(
				numberSchema.optional()
			)
			.returns(z.promise(z.any()))
			.implement(this.internal_compressPdfToStream.bind(this))
			(imageQuality);
	}

	/**
	 * Compress the PDF and save the result directly to a file.
	 * Uses QPdf compression which can significantly reduce file size.
	 * @param filePath The output file path to save the compressed PDF.
	 * @param imageQuality Optional JPEG quality (1-100) for image compression. If omitted, default compression is applied.
	 */
	public async compressAndSaveAs(
		filePath: string,
		imageQuality?: number
	): Promise<void> {
		return await z.function()
			.args(
				stringSchema,
				numberSchema.optional()
			)
			.returns(z.promise(z.void()))
			.implement(this.internal_compressAndSaveAs.bind(this))
			(filePath, imageQuality);
	}

	//#endregion

	//#region linearize
	/**
	 * Linearize the current PDF document and return the result as a {@link Buffer} (byte array).
	 * Produces a web-optimized PDF that can be viewed incrementally in a browser.
	 *
	 * @param mode Linearization strategy. Defaults to {@link LinearizationMode.Automatic}.
	 * @returns A Promise resolving to a Buffer containing the linearized PDF bytes.
	 */
	public async linearizePdfToBytes(
		mode: LinearizationMode = LinearizationMode.Automatic
	): Promise<Buffer> {
		return this.internal_linearizePdfToBytes(mode);
	}

	/**
	 * Linearize the current PDF document and return the result as a {@link Readable} stream.
	 * Useful for piping directly to file streams or HTTP responses without buffering the
	 * entire PDF in memory.
	 *
	 * <b>Note:</b> Stream output is only supported with {@link LinearizationMode.InMemory}
	 * (or {@link LinearizationMode.Automatic} when memory mode is selected). For
	 * {@link LinearizationMode.FileBased} use {@link linearizePdfToBytes} or
	 * {@link saveAsLinearized}.
	 *
	 * @returns A Promise resolving to a Readable stream of the linearized PDF bytes.
	 */
	public async linearizePdfToStream(): Promise<Readable> {
		return this.internal_linearizePdfToStream();
	}

	/**
	 * Linearize the current PDF document and save the result to the given output file path.
	 *
	 * @param outputFilePath The file path to save the linearized PDF.
	 * @param mode Linearization strategy. Defaults to {@link LinearizationMode.Automatic}.
	 */
	public async saveAsLinearized(
		outputFilePath: string,
		mode: LinearizationMode = LinearizationMode.Automatic
	): Promise<void> {
		return await z.function()
			.args(filePathSchema, z.nativeEnum(LinearizationMode).optional())
			.returns(z.promise(z.void()))
			.implement(async (filePath: string, m?: LinearizationMode): Promise<void> => {
				return this.internal_saveAsLinearized(filePath, m ?? LinearizationMode.Automatic);
			})
			(outputFilePath, mode);
	}

	/**
	 * Check whether the PDF file at the given path is linearized ("Fast Web View").
	 *
	 * @param pdfFilePath Path to a PDF file on disk.
	 * @param password    Optional password for encrypted PDFs.
	 * @returns A Promise resolving to true if the file is linearized, false otherwise.
	 */
	public static async isLinearized(
		pdfFilePath: string,
		password = ""
	): Promise<boolean> {
		return await z.function()
			.args(pdfFilePathSchema, stringSchema.optional())
			.returns(z.promise(booleanSchema))
			.implement(async (filePath: string, pwd?: string): Promise<boolean> => {
				const bytes = fs.readFileSync(filePath);
				return await isLinearizedFromBytes(bytes, pwd ?? "");
			})
			(pdfFilePath, password);
	}

	/**
	 * Linearize the given PDF bytes and return the linearized bytes.
	 * Static helper for linearizing PDFs without creating a PdfDocument instance.
	 *
	 * @param pdfBytes Buffer containing the PDF data.
	 * @param password Optional password to decrypt the PDF if encrypted.
	 * @param mode     Linearization strategy. Defaults to {@link LinearizationMode.Automatic}.
	 */
	public static async linearizePdfBytesToBytes(
		pdfBytes: Buffer,
		password = "",
		mode: LinearizationMode = LinearizationMode.Automatic
	): Promise<Buffer> {
		return await z.function()
			.args(bufferSchema, stringSchema.optional(), z.nativeEnum(LinearizationMode).optional())
			.returns(z.promise(z.instanceof(Buffer)))
			.implement(async (bytes: Buffer, pwd?: string, m?: LinearizationMode): Promise<Buffer> => {
				return await linearizeCoreFromBytes(bytes, pwd ?? "", m ?? LinearizationMode.Automatic);
			})
			(pdfBytes, password, mode);
	}

	/**
	 * Linearize the given PDF bytes and save the result to the given output file path.
	 * Static helper for linearizing PDFs without creating a PdfDocument instance.
	 *
	 * @param pdfBytes       Buffer containing the PDF data.
	 * @param outputFilePath The file path to save the linearized PDF.
	 * @param password       Optional password to decrypt the PDF if encrypted.
	 * @param mode           Linearization strategy. Defaults to {@link LinearizationMode.Automatic}.
	 */
	public static async saveAsLinearizedFromBytes(
		pdfBytes: Buffer,
		outputFilePath: string,
		password = "",
		mode: LinearizationMode = LinearizationMode.Automatic
	): Promise<void> {
		return await z.function()
			.args(bufferSchema, filePathSchema, stringSchema.optional(), z.nativeEnum(LinearizationMode).optional())
			.returns(z.promise(z.void()))
			.implement(async (bytes: Buffer, filePath: string, pwd?: string, m?: LinearizationMode): Promise<void> => {
				const linearized = await linearizeCoreFromBytes(bytes, pwd ?? "", m ?? LinearizationMode.Automatic);
				fs.writeFileSync(filePath, linearized);
			})
			(pdfBytes, outputFilePath, password, mode);
	}
	//#endregion

	//#region elementLocations
	/**
	 * Retrieves the rendered page locations of HTML elements that were tracked during rendering.
	 *
	 * To use this feature, set {@link ChromePdfRenderOptions.elementQuerySelectors} with CSS
	 * selectors before rendering. After rendering, call this method to retrieve the page index
	 * and coordinates of each matched element.
	 *
	 * Results are cached on first call and reused on subsequent calls. If you modify the
	 * document's annotations after rendering, call {@link resetElementLocationCache} to force
	 * the cache to be rebuilt on next access.
	 *
	 * Mirrors {@code IronPdf.PdfDocument.GetElementLocations()} on the C# side.
	 *
	 * @returns A list of {@link RenderedElementLocation} objects, one per matched element,
	 * sorted in document order. Returns an empty list if no element query selectors were
	 * configured or no elements matched.
	 */
	public async getElementLocations(): Promise<RenderedElementLocation[]> {
		return this.internal_getElementLocations();
	}

	/**
	 * Clears the cached result of {@link getElementLocations}, forcing a fresh scan of the
	 * document's annotations on the next call. Call this if you've modified the document's
	 * annotations or structure after rendering.
	 */
	public resetElementLocationCache(): void {
		this.cachedElementLocations = null;
	}
	//#endregion

	//#region bookmarks
	/**
	 * Retrieves all outline bookmarks (table-of-contents entries) in this PDF document.
	 *
	 * <p>Bookmarks are returned as a flat list with parent linkage via
	 * {@link Bookmark.parentItemId}. Roots have an empty {@code parentItemId}.
	 * Use {@link Bookmark.hierarchy} for depth information.</p>
	 *
	 * <p>Auto-bookmarks generated from HTML headings (via
	 * {@link ChromePdfRenderOptions.autoBookmarksFromHeadings}) are included.</p>
	 *
	 * <p>Mirrors {@code IronPdf.PdfDocument.Bookmarks} on the C# side.</p>
	 *
	 * @returns A list of {@link Bookmark} objects, sorted in document order.
	 *          Returns an empty list if the document has no bookmarks.
	 */
	public async getBookmarks(): Promise<Bookmark[]> {
		return this.internal_getBookmarks();
	}
	//#endregion

	//#region annotation
	/**
	 * Adds a clickable internal hyperlink annotation that navigates to another page within
	 * the same PDF document. Useful for building custom tables of contents, cross-references,
	 * and in-document navigation.
	 *
	 * The target page and display options are configured via the {@link LinkAnnotation}
	 * fields ({@code destinationPageIndex}, {@code destinationType}, etc.).
	 *
	 * Mirrors {@code IronPdf.Annotations.LinkAnnotation} on the C# side.
	 *
	 * @example
	 * ```ts
	 * await pdf.addLinkAnnotation({
	 *     pageIndex: 0,
	 *     destinationPageIndex: 3,
	 *     x: 72, y: 700,
	 *     width: 300, height: 18,
	 *     contents: "Chapter 1",
	 * });
	 * ```
	 */
	public async addLinkAnnotation(link: LinkAnnotation): Promise<void> {
		return this.internal_addLinkAnnotation(link);
	}

	/**
	 * Retrieves the number of annotations contained on the specified page.
	 *
	 * @param pageIndex Zero-based page index. The first page has a page index of 0.
	 */
	public async getAnnotationCount(pageIndex: number): Promise<number> {
		return this.internal_getAnnotationCount(pageIndex);
	}
	//#endregion

	//#region page
	/**
	 * Gets information of all pages in the PdfDocument
	 */
	public async getPagesInfo(): Promise<PageInfo[]> {
		return this.internal_getPagesInfo()
	}

	/**
	 * Gets the number of pages in the PdfDocument.
	 */
	public async getPageCount(): Promise<number> {
		return this.internal_getPageCount()
	}

	/**
	 * Set the page orientation.
	 * @param pageRotation see {@link PageRotation}
	 * @param options including {@link PdfPageSelection}
	 */
	public async setRotation(
		pageRotation: PageRotation,
		options?: {
			/**
			 * @default "all"
			 */
			pdfPageSelection?: PdfPageSelection | undefined;
		} | undefined
	) {
		return z.function()
			.args(
				pageRotationSchema,
				z.object({pdfPageSelection: pdfPageSelectionSchema.optional()}).optional()
			)
			.returns(z.promise(z.void()))
			.implement(this.internal_setRotation.bind(this))
			(pageRotation, options);
	}

	/**
	 * Resize a page to the specified dimensions
	 * @param newSize {@link PdfPaperSize}
	 * @param options including {@link PdfPageSelection}
	 */
	public async resize(
		newSize: PdfPaperSize,
		options?: {
			/**
			 * @default "all"
			 */
			pdfPageSelection?: PdfPageSelection | undefined;
		} | undefined
	): Promise<void> {
		return z.function()
			.args(
				pdfPaperSizeSchema,
				z.object({pdfPageSelection: pdfPageSelectionSchema.optional()}).optional()
			)
			.returns(z.promise(z.void()))
			.implement(this.internal_resize.bind(this))
			(newSize, options);
	}

	/**
	 * Adds another PDF to the beginning of the current PdfDocument
	 * If AnotherPdfFile contains form fields, those fields will be appended with '_' in the resulting PDF. e.g. 'Name' will be 'Name_'
	 * @param fromPdfDocument PdfDocument to prepend
	 */
	public async prependAnotherPdf(
		fromPdfDocument: PdfDocument
	): Promise<void> {
		return z.function()
			.args(pdfDocumentSchema)
			.returns(z.promise(z.void()))
			.implement(this.internal_prependAnotherPdf.bind(this))
			(fromPdfDocument);
	}

	/**
	 * Appends another PDF to the end of the current <see cref="PdfDocument"/>
	 * If AnotherPdfFile contains form fields, those fields will be appended with '_' in the resulting PDF. e.g. 'Name' will be 'Name_'
	 * @param fromPdfDocument PdfDocument to Append
	 */
	public async appendAnotherPdf(fromPdfDocument: PdfDocument): Promise<void> {
		return z.function()
			.args(pdfDocumentSchema)
			.returns(z.promise(z.void()))
			.implement(this.internal_appendAnotherPdf.bind(this))
			(fromPdfDocument);
	}

	/**
	 * Inserts another PDF into the current PdfDocument, starting at a given Page Index.
	 * If AnotherPdfFile contains form fields, those fields will be appended with '_' in the resulting PDF. e.g. 'Name' will be 'Name_'
	 * @param fromPdfDocument Another PdfDocument
	 * @param insertAtPageIndex Index at which to insert the new content.  Note: Page 1 has index 0...
	 */
	public async insertPagesFromAnotherPdf(
		fromPdfDocument: PdfDocument,
		insertAtPageIndex: number
	): Promise<void> {
		return z.function()
			.args(pdfDocumentSchema, numberSchema.describe("insertAtPageIndex: number"))
			.returns(z.promise(z.void()))
			.implement(this.internal_insertPagesFromAnotherPdf.bind(this))
			(fromPdfDocument, insertAtPageIndex);
	}

	/**
	 * Removes a range of pages from the PDF
	 * @param pages pages to remove
	 */
	public async removePage(pages: PdfPageSelection): Promise<void> {
		return z.function()
			.args(pdfPageSelectionSchema)
			.returns(z.promise(z.void()))
			.implement(this.internal_removePage.bind(this))
			(pages);
	}

	/**
	 * Creates a new PDF by copying a range of pages from this {@link PdfDocument}.
	 * @param pages pages to copy (default "all")
	 */
	public async duplicate(
		pages: PdfPageSelection = "all"
	): Promise<PdfDocument> {
		return z.function()
			.args(pdfPageSelectionSchema)
			.returns(z.promise(pdfDocumentSchema))
			.implement(this.internal_duplicate.bind(this))
			(pages);
	}

	//#endregion

	//#region image
	/**
	 * Finds all embedded Images from within a specified pages in the PDF and returns them as Buffer
	 * @param options including {@link PdfPageSelection}
	 */
	public async extractRawImages(options?: {
		/**
		 * @default "all"
		 */
		fromPages?: PdfPageSelection;
	} | undefined): Promise<Buffer[]> {
		return z.function()
			.args(
				z.object({fromPages: pdfPageSelectionSchema.optional()}).optional()
			)
			.returns(z.promise(bufferArraySchema))
			.implement(this.internal_extractRawImages.bind(this))
			(options);
	}

	/**
	 * Renders the PDF and exports image Files in convenient formats. 1 image file is created for each
	 * page.
	 *
	 * @param options including {@link PdfPageSelection} {@link ImageType}
	 *
	 * @return array of images as Buffer[]
	 */
	public async rasterizeToImageBuffers(options?: {
		/**
		 * @default "all"
		 */
		fromPages?: PdfPageSelection | undefined;
		/**
		 * @default {@link ImageType.PNG}
		 */
		imageType?: ImageType | undefined;
	} | undefined): Promise<Buffer[]> {
		return z.function()
			.args(
				z.object({
					fromPages: pdfPageSelectionSchema.optional(),
					imageType: imageTypeSchema.optional()
				}).optional()
			)
			.returns(z.promise(bufferArraySchema))
			.implement(this.internal_rasterizeToImageBuffers.bind(this))
			(options);
	}

	/**
	 * Renders the PDF and exports image Files in convenient formats. 1 image file is created for each
	 * page. Running number will append output file path.
	 *
	 * @param filePath output file path.
	 * @param options including {@link PdfPageSelection} {@link ImageType}
	 *
	 * @return array of images file name as string[]
	 */
	public async rasterizeToImageFiles(
		filePath: string,
		options?: {
			/**
			 * @default "all"
			 */
			fromPages?: PdfPageSelection | undefined;
			/**
			 * @default {@link ImageType.PNG}
			 */
			type?: ImageType | undefined;
		} | undefined
	): Promise<string[]> {
		return z.function()
			.args(
				filePathSchema,
				z.object({
					fromPages: pdfPageSelectionSchema.optional(),
					type: imageTypeSchema.optional()
				}).optional()
			)
			.returns(z.promise(stringArraySchema))
			.implement(this.internal_rasterizeToImageFiles.bind(this))
			(filePath, options);
	}

	//#endregion

	//#region text
	/**
	 * Replace the specified old text with new text on a given page
	 * @param oldText Old text to remove
	 * @param newText New text to add
	 * @param onPages Page index to search for old text to replace (default "all")
	 */
	public async replaceText(
		oldText: string,
		newText: string,
		onPages?: PdfPageSelection | undefined
	): Promise<void> {
		return z.function()
			.args(
				z.string({description: "oldText: string"}),
				z.string({description: "newText: string"}),
				pdfPageSelectionSchema.optional())
			.returns(z.promise(z.void()))
			.implement(this.internal_replaceText.bind(this))
			(oldText, newText, onPages);
	}

	public async extractText(
		onPages?: PdfPageSelection | undefined
	): Promise<string> {
		return z.function()
			.args(pdfPageSelectionSchema.optional())
			.returns(z.promise(z.string()))
			.implement(this.internal_extractText.bind(this))
			(onPages);
	}

	//#endregion

	//#region pdfA
	/**
	 * Convert the current document into the specified PDF-A standard format
	 * @param pdfaVersion The PDF/A version to convert to (default: PdfA3b)
	 * @param customICC (Optional) Custom color profile file path
	 */
	public async convertToPdfA(pdfaVersion: PdfAVersions = PdfAVersions.PdfA3b, customICC?: string | undefined): Promise<void> {
		return z.function()
			.args(
				z.nativeEnum(PdfAVersions, {description: "pdfaVersion: PdfAVersions"}),
				z.string({description: "customICC: string"}).optional()
			)
			.returns(z.promise(z.void()))
			.implement(this.internal_convertToPdfA.bind(this))
			(pdfaVersion, customICC);
	}

	/**
	 * Render HTML to PDF and convert to PDF/UA format with screen reader support.
	 * Produces a proper semantic structure tree (Sect → H1, P, etc.) for accessibility.
	 * @param html The HTML string to render
	 * @param naturalLanguages Document language for screen readers (default: English)
	 * @param options Optional render options
	 */
	public static async fromHtmlAsPdfUA(
		html: string,
		naturalLanguages: NaturalLanguages = NaturalLanguages.English,
		options?: {
			renderOptions?: ChromePdfRenderOptions | undefined;
		} | undefined
	): Promise<PdfDocument> {
		const pdf = await PdfDocument.fromHtml(html, options);
		await toPdfUAForScreenReader(await pdf.internal_getId(), html, naturalLanguages);
		return pdf;
	}

	/**
	 * Convert the current document into the specified PDF/UA standard format
	 */
	public async convertToPdfUA(naturalLanguages: NaturalLanguages, pdfUaVersion: PdfUAVersions = PdfUAVersions.PdfUA1): Promise<void> {
		return this.internal_convertToPdfUA(naturalLanguages, pdfUaVersion)
	}

	//#endregion

	//#region metadata
	/**
	 * Gets a Map<string, string> of metadata properties
	 */
	public async getMetadata(): Promise<Map<string, string>> {
		return this.internal_getMetadata()
	}

	/**
	 * Add or Update a single metadata property
	 * @param key
	 * @param value
	 */
	public async addOrUpdateMetadata(
		key: string,
		value: string
	): Promise<void> {
		return z.function()
			.args(z.string({description: "key: string"}), z.string({description: "value: string"}))
			.returns(z.promise(z.void()))
			.implement(this.internal_addOrUpdateMetadata.bind(this))
			(key, value);
	}

	/**
	 * Remove a single metadata property
	 * @param key
	 */
	public async removeMetadata(key: string): Promise<void> {
		return z.function()
			.args(z.string({description: "key: string"}))
			.returns(z.promise(z.void()))
			.implement(this.internal_removeMetadata.bind(this))
			(key);
	}

	/**
	 * Sets a whole metadata properties Map<string, string> (override all the metadata property)
	 * @param newMetadataDictionary new metadata properties Map<string, string>
	 */
	public async overrideMetadata(
		newMetadataDictionary: Map<string, string>
	): Promise<void> {
		return z.function()
			.args(mapStringSchema)
			.returns(z.promise(z.void()))
			.implement(this.internal_overrideMetadata.bind(this))
			(newMetadataDictionary);
	}

	//#endregion

	//#region signing
	/**
	 * Sign PDF with digital signature certificate.
	 * Note that the PDF will not be fully signed until Saved
	 * using {@link saveAs} or {@link saveAsBuffer}
	 *
	 * Multiple certificates may be used.
	 * @param signature see {@link DigitalSignature}
	 */
	public async signDigitalSignature(signature: DigitalSignature) {
		return z.function()
			.args(digitalSignatureSchema)
			.returns(z.promise(z.void()))
			.implement(this.internal_signDigitalSignature.bind(this))
			(signature);
	}

	/**
	 * Check if PdfDocument was signed or not
	 */
	public async isSigned(): Promise<boolean> {
		return this.internal_isSigned()
	}

	/**
	 * Count the number signature that signed to this PdfDocument
	 */
	public async signatureCount(): Promise<number> {
		return this.internal_signatureCount()
	}

	/**
	 * Get all verified digital signatures from this PdfDocument.
	 * Returns details including signature name, signer contact, reason, location, date, and validity.
	 * @returns A Promise resolving to an array of {@link VerifiedSignature} objects.
	 */
	public async getVerifiedSignatures(): Promise<VerifiedSignature[]> {
		return this.internal_getVerifiedSignatures()
	}

	//#endregion

	//#region header/footer (affix)
	/**
	 * Apply page header on top of an existing Pdf.
	 * @param header {@link TextAffix}
	 * @param toPages {@link PdfPageSelection}
	 */
	public async addTextHeader(
		header: TextAffix,
		toPages?: PdfPageSelection | undefined
	): Promise<void> {
		return z.function()
			.args(textAffixSchema, pdfPageSelectionSchema.optional())
			.returns(z.promise(z.void()))
			.implement(this.internal_addTextHeader.bind(this))
			(header, toPages);
	}

	/**
	 * Apply page footer on top of an existing Pdf.
	 * @param footer {@link TextAffix}
	 * @param toPages {@link PdfPageSelection}
	 */
	public async addTextFooter(
		footer: TextAffix,
		toPages?: PdfPageSelection | undefined
	): Promise<void> {
		return z.function()
			.args(textAffixSchema, pdfPageSelectionSchema.optional())
			.returns(z.promise(z.void()))
			.implement(this.internal_addTextFooter.bind(this))
			(footer, toPages);
	}

	/**
	 * Apply HTML header on top of an existing Pdf.
	 * @param header {@link HtmlAffix}
	 * @param toPages {@link PdfPageSelection}
	 */
	public async addHtmlHeader(
		header: HtmlAffix,
		toPages?: PdfPageSelection | undefined
	): Promise<void> {
		return z.function()
			.args(htmlAffixSchema, pdfPageSelectionSchema.optional())
			.returns(z.promise(z.void()))
			.implement(this.internal_addHtmlHeader.bind(this))
			(header, toPages);
	}

	/**
	 * Apply HTML footer on top of an existing Pdf.
	 * @param footer {@link HtmlAffix}
	 * @param toPages {@link PdfPageSelection}
	 */
	public async addHtmlFooter(
		footer: HtmlAffix,
		toPages?: PdfPageSelection | undefined
	): Promise<void> {
		return z.function()
			.args(htmlAffixSchema, pdfPageSelectionSchema.optional())
			.returns(z.promise(z.void()))
			.implement(this.internal_addHtmlFooter.bind(this))
			(footer, toPages);
	}

	//#endregion

	//#region stamp
	/**
	 * Edits the PDF by applying the HTML's rendered to only selected page(s).
	 * @param htmlStringOrHtmlFilePath
	 * @param options including {@link HtmlStampOptions} {@link PdfPageSelection}
	 */
	public async stampHtml(
		htmlStringOrHtmlFilePath: HtmlFilePath | HtmlString,
		options?: {
			htmlStampOptions?: HtmlStampOptions | undefined;
			toPages?: PdfPageSelection | undefined;
		} | undefined
	) {
		return z.function()
			.args(z.union([htmlFilePathSchema, htmlStringSchema]), z.object({
				htmlStampOptions: htmlStampOptionsSchema.optional(),
				toPages: pdfPageSelectionSchema.optional()
			}).optional())
			.returns(z.promise(z.void()))
			.implement(this.internal_stampHtml.bind(this))
			(htmlStringOrHtmlFilePath, options);
	}

	/**
	 * Edits the PDF by applying the image to only selected page(s).
	 * @param image image file path or image buffer
	 * @param options including {@link ImageStampOptions} {@link PdfPageSelection}
	 */
	public async stampImage(
		image: ImageFilePath | ImageBuffer,
		options?: {
			imageStampOptions?: ImageStampOptions | undefined;
			toPages?: PdfPageSelection | undefined;
		} | undefined
	) {
		return z.function()
			.args(z.union([imageFilePathSchema, imageBufferSchema]), z.object({
				imageStampOptions: imageStampOptionsSchema.optional(),
				toPages: pdfPageSelectionSchema.optional()
			}).optional())
			.returns(z.promise(z.void()))
			.implement(this.internal_stampImage.bind(this))
			(image, options);
	}

	/**
	 * Edits the PDF by applying the text to only selected page(s).
	 * @param text text to stamp
	 * @param options including {@link TextStampOptions} {@link PdfPageSelection}
	 */
	public async stampText(
		text: string,
		options?: {
			textStampOptions?: TextStampOptions | undefined;
			toPages?: PdfPageSelection | undefined;
		} | undefined
	) {
		return z.function()
			.args(stringSchema, z.object({
				textStampOptions: textStampOptionsSchema.optional(),
				toPages: pdfPageSelectionSchema.optional()
			}).optional())
			.returns(z.promise(z.void()))
			.implement(this.internal_stampText.bind(this))
			(text, options);
	}

	/**
	 * Edits the PDF by applying the barcode to only selected page(s).
	 * @param barcodeValue barcode
	 * @param options including {@link BarcodeType} {@link BarcodeStampOptions} {@link PdfPageSelection}
	 */
	public async stampBarcode(
		barcodeValue: string,
		options?: {
			barcodeEncoding: BarcodeType;
			barcodeStampOptions?: BarcodeStampOptions | undefined;
			toPages?: PdfPageSelection | undefined;
		} | undefined
	) {
		return z.function()
			.args(stringSchema, z.object({
				barcodeEncoding: barcodeTypeSchema,
				barcodeStampOptions: barcodeStampOptionsSchema.optional(),
				toPages: pdfPageSelectionSchema.optional()
			}).optional())
			.returns(z.promise(z.void()))
			.implement(this.internal_stampBarcode.bind(this))
			(barcodeValue, options);
	}

	//#endregion

	//#region background/foreground
	/**
	 * Adds a background to each page of this PDF. The background is copied from a first page in the
	 * backgroundPdf document.
	 *
	 * @param fromPdf background PDF document
	 * @param sourcePageIndex Index (zero-based page number) of the page to copy from the Background/Foreground PDF. Default is 0.
	 * @param applyToPages  PageSelection to which the background/foreground will be added. Default is "all"
	 */
	public async addBackgroundFromAnotherPdf(
		fromPdf: PdfDocument,
		sourcePageIndex = 0,
		applyToPages?: PdfPageSelection | undefined
	): Promise<void> {
		return z.function()
			.args(pdfDocumentSchema, numberSchema, pdfPageSelectionSchema.optional())
			.returns(z.promise(z.void()))
			.implement(this.internal_addBackgroundFromAnotherPdf.bind(this))
			(fromPdf, sourcePageIndex, applyToPages);
	}

	/**
	 * Adds a foreground to each page of this PDF. The background is copied from a first page in the
	 * backgroundPdf document.
	 *
	 * @param fromPdf foreground PDF document
	 * @param sourcePageIndex Index (zero-based page number) of the page to copy from the Background/Foreground PDF. Default is 0.
	 * @param applyToPages  PageSelection to which the background/foreground will be added. Default is "all"
	 */
	public async addForegroundFromAnotherPdf(
		fromPdf: PdfDocument,
		sourcePageIndex = 0,
		applyToPages?: PdfPageSelection | undefined
	): Promise<void> {
		return z.function()
			.args(pdfDocumentSchema, numberSchema, pdfPageSelectionSchema.optional())
			.returns(z.promise(z.void()))
			.implement(this.internal_addForegroundFromAnotherPdf.bind(this))
			(fromPdf, sourcePageIndex, applyToPages);
	}

	//#endregion

	//#region security
	/**
	 * Removes all user and owner password security for a PDF document.  Also disables content
	 * encryption.
	 * If content is encrypted at 128 bit, copy and paste of content, annotations and form editing may be disabled.
	 */
	public async removePasswordsAndEncryption(): Promise<void> {
		return this.internal_removePasswordsAndEncryption()
	}

	/**
	 * Sets the user password and enables 128Bit encryption of PDF content.
	 * A user password is a password that each user must enter to open or print the PDF document.
	 */
	public async setUserPassword(userPassword: string): Promise<void> {
		return z.function()
			.args(z.string({description: "userPassword: string"}))
			.returns(z.promise(z.void()))
			.implement(this.internal_setUserPassword.bind(this))
			(userPassword);
	}

	/**
	 * Sets the owner password and enables 128Bit encryption of PDF content. An owner password is one used to
	 * enable and disable all other security settings. <para>OwnerPassword must be set to a non-empty string
	 * value for {@link PdfPermission.AllowAccessibilityExtractContent} ,  {@link PdfPermission.AllowAnnotations} ,
	 * {@link PdfPermission.AllowFillForms},  {@link PdfPermission.AllowPrint},  {@link PdfPermission.AllowModify} to be
	 * restricted.
	 */
	public async setOwnerPassword(ownerPassword: string): Promise<void> {
		return z.function()
			.args(z.string({description: "ownerPassword: string"}))
			.returns(z.promise(z.void()))
			.implement(this.internal_setOwnerPassword.bind(this))
			(ownerPassword);
	}

	/**
	 * Sets the permissions of this PdfDocument
	 * @param permissions see {@link PdfPermission}
	 */
	public async setPermission(permissions: PdfPermission): Promise<void> {
		return z.function()
			.args(pdfPermissionSchema)
			.returns(z.promise(z.void()))
			.implement(this.internal_setPermission.bind(this))
			(permissions);
	}

	/**
	 * Gets the current permissions of this PdfDocument
	 * @return {@link PdfPermission}
	 */
	public async getPermission(): Promise<PdfPermission> {
		return this.internal_getPermission()
	}

	/**
	 * Makes this PDF document read only such that: Content is encrypted at 128 bit. Copy and paste of
	 * content is disallowed. Annotations and form editing are disabled.
	 * @param ownerPassword The owner password for the PDF. A string for owner password is required to enable PDF encryption and
	 * all document security options.
	 */
	public async makePdfDocumentReadOnly(ownerPassword: string): Promise<void> {
		return z.function()
			.args(z.string({description: "ownerPassword: string"}))
			.returns(z.promise(z.void()))
			.implement(this.internal_makePdfDocumentReadOnly.bind(this))
			(ownerPassword);
	}

	//#endregion

	//#region close
	/**
	 * Dispose this PdfDocument object (clean up the resource)
	 * This is necessary to free the memory used by PdfDocument. See {@link cleanUp}
	 * Once this method was called this PdfDocument object no longer usable.
	 */
	public async close() {
		return this.internal_close()
	}

	//#endregion

	//#region ALL_INTERNAL_STUFF
	/**
	 * Internal PDF document ID
	 * @private
	 */
	private pdfDocumentId?: string | undefined;
	/**
	 * @private
	 */
	private readonly promiseDocumentId?: Promise<string> | undefined;
	/**
	 * @private
	 */
	private pdfPassword?: PdfPassword | undefined;

	/**
	 * Create a PdfDocument object from a {@link PdfInput}
	 * For more specific way to create/open PdfDocument see {@link fromUrl} {@link fromZip} {@link fromHtml} {@link fromImage}  {@link open}
	 *
	 * @param pdfInput see {@link PdfInput} (required) (pdfInput is {@link PdfFilePath} or {@link Buffer})
	 * @param password a password to open the PDF required if PDF file was private.
	 * @param trackChanges Optionally track changes to the document (for use with incremental saves)
	 */
	constructor(pdfInput?: PdfInput | undefined, password?: PdfPassword | undefined, trackChanges?: ChangeTrackingModes | undefined) {
		if (pdfInput) {
			this.pdfDocumentId = undefined;
			const input = separatePdfInput(pdfInput);
			switch (input.type) {
				case "htmlFile":
					this.promiseDocumentId = renderHtmlFileToPdf(input.htmlFile);
					break;
				case "htmlString":
					this.promiseDocumentId = renderHtmlToPdf(input.htmlString);
					break;
				case "zipFile":
					this.promiseDocumentId = renderHtmlZipToPdf(input.zipFile);
					break;
				case "buffer":
					this.promiseDocumentId = openPdfFileBuffer(
						input.buffer,
						{userPassword: password?.userPassword, ownerPassword: password?.ownerPassword, trackChanges: trackChanges}
					);
					break;
				case "pdfFile":
					this.pdfPassword = password;
					this.promiseDocumentId = openPdfFileBuffer(
						fs.readFileSync(input.pdfFile),
						{userPassword: password?.userPassword, ownerPassword: password?.ownerPassword, trackChanges: trackChanges}
					);
					break;
				case "url":
					this.promiseDocumentId = renderUrlToPdf(input.url);
					break;
				case "pdfDocument":
					this.pdfDocumentId = input.pdfDocument.pdfDocumentId;
					this.promiseDocumentId =
						input.pdfDocument.promiseDocumentId;
					break;
			}
			if (this.pdfDocumentId) {
				Access.usedDocumentIds.add(this.pdfDocumentId);
			}
			this.promiseDocumentId?.then((id) =>
				Access.usedDocumentIds.add(id)
			);
		}
	}

	/**
	 * Dispose this PdfDocument object (clean up the resource)
	 * This is necessary to free the memory used by PdfDocument. See {@link cleanUp}
	 * Once this method was called this PdfDocument object no longer usable.
	 */
	private async internal_close() {
		await disposePdf(await this.internal_getId());
	}

	/**
	 * @private
	 */
	private async internal_getId(): Promise<string> {
		if (this.pdfDocumentId) {
			return Promise.resolve(this.pdfDocumentId) ;
		} else if (this.promiseDocumentId) {
			this.pdfDocumentId = await this.promiseDocumentId;
			return Promise.resolve(this.pdfDocumentId);
		} else {
			throw new Error("Cannot Get PdfDocumentId");
		}
	}

	//#region io

	/**
	 * Open or Create a PdfDocument from a {@link PdfInput}
	 * @param pdfInput {@link PdfInput}
	 * @param options including {@link PdfPassword} {@link ChromePdfRenderOptions} {@link HttpLoginCredentials} mainHtmlFile
	 */
	private static async internal_open(
		pdfInput: PdfInput,
		options?: {
			/**
			 * required for open a private PDF file
			 * @default undefined
			 */
			password?: PdfPassword | undefined;
			/**
			 * Apply renderOptions if PdfInput is a {@link HtmlString} or {@link HtmlFilePath} or {@link ZipFilePath} or {@link Url}}
			 * @default undefined
			 */
			renderOptions?: ChromePdfRenderOptions | undefined;
			/**
			 * Apply httpLoginCredentials if PdfInput is a {@link HtmlString} or {@link HtmlFilePath} or {@link ZipFilePath} or {@link Url}}
			 * @default undefined
			 */
			httpLoginCredentials?: HttpLoginCredentials | undefined;
			/**
			 * Apply mainHtmlFile if PdfInput is {@link ZipFilePath}
			 * @default index.html
			 */
			mainHtmlFile?: string | undefined;
			/**
			 * Optionally track changes to the document (for use with incremental saves)
			 * @default {@link ChangeTrackingModes.AutoChangeTracking}
			 */
			trackChanges?: ChangeTrackingModes | undefined;
			// /**
			//  * Apply baseUrl if
			//  * The HTML base URL for which references to external CSS, Javascript and Image files will be relative.
			//  * @default undefined
			//  */
			// baseUrl ?: string | undefined;  //not supported
		} | undefined
	): Promise<PdfDocument> {
		if (pdfInput) {
			const input = separatePdfInput(pdfInput);
			switch (input.type) {
				case "htmlFile":
					const newHtmlFilePdf = new PdfDocument();
					newHtmlFilePdf.pdfDocumentId = await renderHtmlFileToPdf(
						input.htmlFile,
						options?.renderOptions
					);
					return newHtmlFilePdf;
				case "htmlString":
					const newHtmlStringPdf = new PdfDocument();
					newHtmlStringPdf.pdfDocumentId = await renderHtmlToPdf(
						input.htmlString,
						options?.renderOptions
					);
					return newHtmlStringPdf;
				case "zipFile":
					const newZipFilePdf = new PdfDocument();
					newZipFilePdf.pdfDocumentId = await renderHtmlZipToPdf(
						input.zipFile,
						options?.mainHtmlFile,
						options?.renderOptions
					);
					return newZipFilePdf;
				case "buffer":
					const newBufferPdf = new PdfDocument(
						undefined,
						options?.password,
						options?.trackChanges
					);
					newBufferPdf.pdfDocumentId = await openPdfFileBuffer(
						input.buffer,{
							userPassword: options?.password?.userPassword,
							ownerPassword: options?.password?.ownerPassword,
							trackChanges: options?.trackChanges
						}
					);
					return newBufferPdf;
				case "pdfFile":
					const newPdfFilePdf = new PdfDocument(
						undefined,
						options?.password
					);
					newPdfFilePdf.pdfDocumentId = await openPdfFileBuffer(
						fs.readFileSync(input.pdfFile),
						{
							userPassword: options?.password?.userPassword,
							ownerPassword: options?.password?.ownerPassword,
							trackChanges: options?.trackChanges
						}
					);

					return newPdfFilePdf;
				case "url":
					const newUrlPdf = new PdfDocument();
					newUrlPdf.pdfDocumentId = await renderUrlToPdf(input.url, {
						renderOptions: options?.renderOptions,
						httpLoginCredentials: options?.httpLoginCredentials,
					});
					return newUrlPdf;
				case "pdfDocument":
					return Promise.resolve(input.pdfDocument);
			}
		}
		throw new Error(`cannot create PdfDocument object from ${pdfInput}`);
	}

	/**
	 * Open a PdfDocument from .pdf file
	 * @param pdfFilePath A path to .pdf file
	 * @param Optionally track changes to the document (for use with incremental saves)
	 */
	private static async internal_fromFile(
		pdfFilePath: string,
		trackChanges?: ChangeTrackingModes | undefined
	): Promise<PdfDocument> {
		return this.internal_open(pdfFilePath, {trackChanges: trackChanges});
	}

	/**
	 * Create a PdfDocument from an Url
	 * @param url A website Url
	 * @param options including {@link ChromePdfRenderOptions}
	 */
	private static async internal_fromUrl(
		url: URL | string,
		options?: {
			/**
			 * Apply renderOptions if PdfInput is a {@link HtmlString} or {@link HtmlFilePath} or {@link ZipFilePath} or {@link Url}}
			 * @default undefined
			 */
			renderOptions?: ChromePdfRenderOptions | undefined;
		} | undefined
	): Promise<PdfDocument> {
		return this.internal_open(url, options);
	}

	/**
	 * Creates a PDF file from a local Zip file, and returns it as a {@link PdfDocument}.
	 * IronPDF is a W3C standards compliant HTML rendering based on Google's Chromium browser.
	 * If your output PDF does not look as expected:
	 *
	 * - Validate your HTML file using  https://validator.w3.org/ &amp; CSS https://jigsaw.w3.org/css-validator/
	 *
	 * - To debug HTML, view the file in Chrome web browser's print preview which will work almost exactly as IronPDF.
	 *
	 * - Read our detailed documentation on pixel perfect HTML to PDF: https://ironpdf.com/tutorials/pixel-perfect-html-to-pdf/
	 *
	 * @param zipFilePath Path to a Zip to be rendered as a PDF.
	 * @param options including {@link ChromePdfRenderOptions} and `mainHtmlFile` a main .html file default: `index.html`
	 */
	private static async internal_fromZip(
		zipFilePath: string,
		options?: {
			/**
			 * Apply renderOptions if PdfInput is a {@link HtmlString} or {@link HtmlFilePath} or {@link ZipFilePath} or {@link Url}}
			 * @default undefined
			 */
			renderOptions?: ChromePdfRenderOptions | undefined;
			/**
			 * a main .html file default: `index.html`
			 */
			mainHtmlFile?: string | undefined;
		} | undefined
	): Promise<PdfDocument> {
		return this.internal_open(zipFilePath, options);
	}

	/**
	 * Creates a PDF file from a Html string, and returns it as an {@link PdfDocument} object which can be edited and saved to disk or served on a website.
	 * @param htmlStringOrHtmlFilePath The Html to be rendered as a PDF.
	 * @param options including {@link ChromePdfRenderOptions}
	 */
	private static async internal_fromHtml(
		htmlStringOrHtmlFilePath: string,
		options?: {
			/**
			 * Apply renderOptions if PdfInput is a {@link HtmlString} or {@link HtmlFilePath} or {@link ZipFilePath} or {@link Url}}
			 * @default undefined
			 */
			renderOptions?: ChromePdfRenderOptions | undefined;
		} | undefined
	): Promise<PdfDocument> {
		return this.internal_open(htmlStringOrHtmlFilePath, options);
	}

	/**
	 *  Converts multiple image files to a PDF document.  Each image creates 1 page which matches the image
	 *  dimensions. The default PaperSize is A4. You can set it via ImageToPdfConverter.PaperSize.
	 *  Note: Imaging.ImageBehavior.CropPage will set PaperSize equal to ImageSize.
	 * @param images The image file path name(s) or {@link ImageBuffer} object(s)
	 * @param options including {@link ImageToPdfOptions}
	 */
	private static async internal_fromImage(
		images: ImageFilePath | ImageFilePath[] | ImageBuffer | ImageBuffer[],
		options?: {
			/**
			 * Apply renderOptions if PdfInput is a {@link HtmlString} or {@link HtmlFilePath} or {@link ZipFilePath} or {@link Url}}
			 * @default undefined
			 */
			imageToPdfOptions?: ImageToPdfOptions | undefined;
		} | undefined
	): Promise<PdfDocument> {
		let temp: Promise<string>;

		if (Array.isArray(images)) {
			// const imageArray = images as Array<any>;
			if(images[0]){
				const t = separateImageBufferOrImagePathInput(images[0]);

				switch (t.type) {
					case "imageBuffer":
						temp = renderImagesBufferToPdf(
							images as Buffer[],
							options?.imageToPdfOptions
						);
						break;
					case "imageFilePath":
						temp = renderImagesFilesToPdf(
							images as string[],
							options?.imageToPdfOptions
						);
						break;
				}
			}else{
				throw new Error("imageToPdf input Array is Empty");
			}
		} else {
			const image = separateImageBufferOrImagePathInput(images);
			switch (image.type) {
				case "imageBuffer":
					temp = renderImagesBufferToPdf(
						[image.imageBuffer],
						options?.imageToPdfOptions
					);
					break;
				case "imageFilePath":
					temp = renderImagesFilesToPdf(
						[image.imageFilePath],
						options?.imageToPdfOptions
					);
					break;
			}
		}

		if (!temp) {
			throw new Error(`cannot read image: ${images}`);
		}

		const newUrlPdf = new PdfDocument();
		newUrlPdf.pdfDocumentId = await temp;
		return newUrlPdf;
	}

	/**
	 * Static method that joins (concatenates) multiple PDF documents together into one compiled PDF document.
	 * If the PDF contains form fields the form field in the resulting PDF's name will be appended with '_{index}' e.g. 'Name' will be 'Name_0'
	 * @param pdfs array of PDF
	 */
	private static async internal_mergePdf(pdfs: PdfInput[]): Promise<PdfDocument> {
		const ids = await Promise.all(
			pdfs.map(async (x) => (await PdfDocument.open(x)).internal_getId())
		);
		const newDocId = mergePdfs(ids);
		const newUrlPdf = new PdfDocument();
		newUrlPdf.pdfDocumentId = await newDocId;
		return newUrlPdf;
	}

	/**
	 * Saves the PdfDocument to a file.
	 * @param filePath Target file path
	 * @param saveOptions see {@link SaveOptions}
	 */
	private async internal_saveAs(filePath: string, saveOptions?: SaveOptions | undefined): Promise<void> {
		return this.internal_saveAsBuffer(saveOptions).then((pdfFileBuffer) => {
			fs.writeFile(filePath, pdfFileBuffer, "binary", (err) => {
				if (err) throw err;
			});
		});
	}

	/**
	 * Saves the PdfDocument to a binary (Buffer)
	 * @param saveOptions see {@link SaveOptions}
	 */
	private async internal_saveAsBuffer(saveOptions?: SaveOptions | undefined): Promise<Buffer> {
		return getBinaryData(await this.internal_getId(), saveOptions);
	}

	//#endregion

	//#region compress
	/**
	 * Compress existing PDF images using JPG encoding and the specified settings
	 * @param imageQuality Quality (1 - 100) to use during compression
	 * @param scaleToVisibleSize Scale down the image resolution according to its visible size in the PDF document
	 */
	private async internal_compressSize(
		imageQuality: number,
		scaleToVisibleSize = false
	): Promise<void> {
		if (imageQuality < 1 || imageQuality > 100)
			throw new Error(
				`Invalid quality specifier (${imageQuality}) when compressing images. Quality must be between 1 and 100.`
			);
		return await compressImage(
			await this.internal_getId(),
			imageQuality,
			scaleToVisibleSize
		);
	}

	/**
	 *  Remove document struct tree information which describes the logical layout of the document.
	 *  Removing the "structure tree" can significantly reduce the disk space used by the document.
	 *  Removing the "structure tree" of a complicated document can negatively impact text selection.
	 */
	private async internal_compressStructTree(): Promise<void> {
		return await compressStructTree(await this.internal_getId());
	}

	/**
	 * Compress the PDF and return the result as a Buffer (byte array).
	 * Uses QPdf compression which can significantly reduce file size.
	 * @param imageQuality Optional JPEG quality (1-100) for image compression. If omitted, default compression is applied.
	 */
	private async internal_compressPdfToBytes(
		imageQuality?: number
	): Promise<Buffer> {
		if (imageQuality !== undefined && (imageQuality < 1 || imageQuality > 100))
			throw new Error(
				`Invalid quality specifier (${imageQuality}) when compressing PDF. Quality must be between 1 and 100.`
			);
		return await compressInMemory(
			await this.internal_getId(),
			imageQuality
		);
	}

	/**
	 * Compress the PDF and return the result as a Readable stream.
	 * Uses QPdf compression which can significantly reduce file size.
	 * Useful for piping directly to file streams or HTTP responses without buffering the entire PDF in memory.
	 * @param imageQuality Optional JPEG quality (1-100) for image compression. If omitted, default compression is applied.
	 */
	private async internal_compressPdfToStream(
		imageQuality?: number
	): Promise<Readable> {
		if (imageQuality !== undefined && (imageQuality < 1 || imageQuality > 100))
			throw new Error(
				`Invalid quality specifier (${imageQuality}) when compressing PDF. Quality must be between 1 and 100.`
			);
		return await compressInMemoryStream(
			await this.internal_getId(),
			imageQuality
		);
	}

	/**
	 * Compress the PDF and save the result directly to a file.
	 * Uses QPdf compression which can significantly reduce file size.
	 * @param filePath The output file path to save the compressed PDF.
	 * @param imageQuality Optional JPEG quality (1-100) for image compression. If omitted, default compression is applied.
	 */
	private async internal_compressAndSaveAs(
		filePath: string,
		imageQuality?: number
	): Promise<void> {
		if (imageQuality !== undefined && (imageQuality < 1 || imageQuality > 100))
			throw new Error(
				`Invalid quality specifier (${imageQuality}) when compressing PDF. Quality must be between 1 and 100.`
			);
		return await compressAndSaveAs(
			await this.internal_getId(),
			filePath,
			imageQuality
		);
	}

	//#endregion

	//#region linearize
	/**
	 * Linearize the current PDF document and return the result as a Buffer.
	 */
	private async internal_linearizePdfToBytes(mode: LinearizationMode): Promise<Buffer> {
		const id = await this.internal_getId();
		return await linearizeCoreFromId(id, () => this.internal_saveAsBuffer(), "", mode);
	}

	/**
	 * Linearize the current PDF document and return the result as a Readable stream.
	 * Always uses the in-memory streaming RPC.
	 */
	private async internal_linearizePdfToStream(): Promise<Readable> {
		return await linearizeInMemoryFromIdStream(await this.internal_getId());
	}

	/**
	 * Linearize the current PDF document and save the result to the given output file path.
	 */
	private async internal_saveAsLinearized(outputFilePath: string, mode: LinearizationMode): Promise<void> {
		const id = await this.internal_getId();
		const linearized = await linearizeCoreFromId(id, () => this.internal_saveAsBuffer(), "", mode);
		fs.writeFileSync(outputFilePath, linearized);
	}
	//#endregion

	//#region elementLocations
	/**
	 * Prefix used by the engine-side element query JavaScript extension when injecting anchor
	 * markers. Must match {@code IronPdf.TableOfContents.ElementQueryJavascriptExtension.ELEMENT_QUERY_PREFIX}
	 * on the C# side.
	 */
	private static readonly ELEMENT_QUERY_PREFIX = "ironpdf-elq-";

	/**
	 * Cache for {@link getElementLocations}. Built lazily on first call.
	 *
	 * The underlying element-query anchor annotations are written during rendering and are
	 * not expected to be mutated afterwards, so a simple populate-once cache is appropriate.
	 * Callers who modify the document's annotations after rendering should call
	 * {@link resetElementLocationCache} to force a rebuild.
	 */
	private cachedElementLocations: RenderedElementLocation[] | null = null;

	private async internal_getElementLocations(): Promise<RenderedElementLocation[]> {
		if (this.cachedElementLocations !== null) {
			return this.cachedElementLocations;
		}

		const id = await this.internal_getId();
		const pageInfos = await getPageInfo(id);
		const locations: RenderedElementLocation[] = [];

		for (let pageIdx = 0; pageIdx < pageInfos.length; pageIdx++) {
			const annotations = await getAnnotations(id, pageIdx);

			for (const wrapped of annotations) {
				if (!wrapped.link) continue;
				const dest = wrapped.link.dest;
				if (!dest || !dest.startsWith(PdfDocument.ELEMENT_QUERY_PREFIX)) continue;

				const remainder = dest.substring(PdfDocument.ELEMENT_QUERY_PREFIX.length);
				const dashIdx = remainder.indexOf("-");
				if (dashIdx <= 0) continue;

				const indexStr = remainder.substring(0, dashIdx);
				const elementIndex = parseInt(indexStr, 10);
				if (isNaN(elementIndex)) continue;

				const encodedText = remainder.substring(dashIdx + 1);
				let text: string;
				try {
					text = decodeURIComponent(encodedText);
				} catch {
					text = encodedText;
				}

				const innerRect = wrapped.link.annotation?.rectangle;
				const rectangle = innerRect
					? {
							x: innerRect.x ?? 0,
							y: innerRect.y ?? 0,
							width: innerRect.width ?? 0,
							height: innerRect.height ?? 0,
					  }
					: {x: 0, y: 0, width: 0, height: 0};

				locations.push({text, pageIndex: pageIdx, rectangle, elementIndex});
			}
		}

		locations.sort((a, b) => a.elementIndex - b.elementIndex);
		this.cachedElementLocations = locations;
		return this.cachedElementLocations;
	}
	//#endregion

	//#region annotation
	private async internal_addLinkAnnotation(link: LinkAnnotation): Promise<void> {
		if (!link) {
			throw new Error("link annotation cannot be null or undefined");
		}
		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");
		}
		await addLinkAnnotation(await this.internal_getId(), link);
	}

	private async internal_getAnnotationCount(pageIndex: number): Promise<number> {
		if (pageIndex < 0) {
			throw new Error("Invalid page index when getting annotation count");
		}
		return await getAnnotationCount(await this.internal_getId(), pageIndex);
	}
	//#endregion

	//#region bookmarks
	private async internal_getBookmarks(): Promise<Bookmark[]> {
		return await getBookmarks(await this.internal_getId());
	}
	//#endregion

	//#region page
	/**
	 * Gets information of all pages in the PdfDocument
	 */
	private async internal_getPagesInfo(): Promise<PageInfo[]> {
		return await getPageInfo(await this.internal_getId());
	}

	/**
	 * Gets the number of pages in the PdfDocument.
	 */
	private async internal_getPageCount(): Promise<number> {
		return (await this.internal_getPagesInfo()).length;
	}

	/**
	 * Set the page orientation.
	 * @param pageRotation see {@link PageRotation}
	 * @param options including {@link PdfPageSelection}
	 */
	private async internal_setRotation(
		pageRotation: PageRotation,
		options?: {
			/**
			 * @default "all"
			 */
			pdfPageSelection?: PdfPageSelection | undefined;
		} | undefined
	) {
		return await setPageRotation(await this.internal_getId(), pageRotation, options);
	}

	/**
	 * Resize a page to the specified dimensions
	 * @param newSize {@link PdfPaperSize}
	 * @param options including {@link PdfPageSelection}
	 */
	private async internal_resize(
		newSize: PdfPaperSize,
		options?: {
			/**
			 * @default "all"
			 */
			pdfPageSelection?: PdfPageSelection | undefined;
		} | undefined
	): Promise<void> {
		return await resizePage(await this.internal_getId(), newSize, options);
	}

	/**
	 * Adds another PDF to the beginning of the current PdfDocument
	 * If AnotherPdfFile contains form fields, those fields will be appended with '_' in the resulting PDF. e.g. 'Name' will be 'Name_'
	 * @param fromPdfDocument PdfDocument to prepend
	 */
	private async internal_prependAnotherPdf(
		fromPdfDocument: PdfDocument
	): Promise<void> {
		await this.internal_insertPagesFromAnotherPdf(fromPdfDocument, 0);
	}

	/**
	 * Appends another PDF to the end of the current <see cref="PdfDocument"/>
	 * If AnotherPdfFile contains form fields, those fields will be appended with '_' in the resulting PDF. e.g. 'Name' will be 'Name_'
	 * @param fromPdfDocument PdfDocument to Append
	 */
	private async internal_appendAnotherPdf(fromPdfDocument: PdfDocument): Promise<void> {
		await this.internal_insertPagesFromAnotherPdf(
			fromPdfDocument,
			(await this.internal_getPageCount())
		);
	}

	/**
	 * Inserts another PDF into the current PdfDocument, starting at a given Page Index.
	 * If AnotherPdfFile contains form fields, those fields will be appended with '_' in the resulting PDF. e.g. 'Name' will be 'Name_'
	 * @param fromPdfDocument Another PdfDocument
	 * @param insertAtPageIndex Index at which to insert the new content.  Note: Page 1 has index 0...
	 */
	private async internal_insertPagesFromAnotherPdf(
		fromPdfDocument: PdfDocument,
		insertAtPageIndex: number
	): Promise<void> {
		await insertPdf(
			await this.internal_getId(),
			await fromPdfDocument.internal_getId(),
			insertAtPageIndex
		);
	}

	/**
	 * Removes a range of pages from the PDF
	 * @param pages pages to remove
	 */
	private async internal_removePage(pages: PdfPageSelection): Promise<void> {
		await removePage(await this.internal_getId(), {PdfPageSelection: pages});
	}

	/**
	 * Creates a new PDF by copying a range of pages from this {@link PdfDocument}.
	 * @param pages pages to copy (default "all")
	 */
	private async internal_duplicate(
		pages: PdfPageSelection = "all"
	): Promise<PdfDocument> {
		const newPdf = new PdfDocument();
		newPdf.pdfDocumentId = await duplicate(await this.internal_getId(), {
			PdfPageSelection: pages,
		});
		return newPdf;
	}

	//#endregion

	//#region image
	/**
	 * Finds all embedded Images from within a specified pages in the PDF and returns them as Buffer
	 * @param options including {@link PdfPageSelection}
	 */
	private async internal_extractRawImages(options?: {
		/**
		 * @default "all"
		 */
		fromPages?: PdfPageSelection | undefined;
	} | undefined): Promise<Buffer[]> {
		return extractRawImages(
			await this.internal_getId(),
			options?.fromPages ?? "all"
		);
	}

	/**
	 * Renders the PDF and exports image Files in convenient formats. 1 image file is created for each
	 * page.
	 *
	 * @param options including {@link PdfPageSelection} {@link ImageType}
	 *
	 * @return array of images as Buffer[]
	 */
	private async internal_rasterizeToImageBuffers(options?: {
		/**
		 * @default "all"
		 */
		fromPages?: PdfPageSelection | undefined;
		/**
		 * @default {@link ImageType.PNG}
		 */
		imageType?: ImageType | undefined;
	}): Promise<Buffer[]> {
		const images = await rasterizeToImageBuffers(
			await this.internal_getId(),
			options?.fromPages ?? "all"
		);
		const jimpImageType: "image/bmp" | "image/png" | "image/jpeg" = (() => {
			switch (options?.imageType) {
				case ImageType.BMP:
					return Jimp.MIME_BMP;
				case ImageType.PNG:
					return Jimp.MIME_PNG;
				case ImageType.JPG:
					return Jimp.MIME_JPEG;
				default:
					return Jimp.MIME_PNG;
			}
		})();
		return Promise.all(
			images.map(async (imageBuffer) => {
				//convert output type
				const jimp = await Jimp.read(imageBuffer);
				return await jimp.getBufferAsync(jimpImageType);
			})
		);
	}

	/**
	 * Renders the PDF and exports image Files in convenient formats. 1 image file is created for each
	 * page. Running number will append output file path.
	 *
	 * @param filePath output file path.
	 * @param options including {@link PdfPageSelection} {@link ImageType}
	 *
	 * @return array of images file name as string[]
	 */
	private async internal_rasterizeToImageFiles(
		filePath: string,
		options?: {
			/**
			 * @default "all"
			 */
			fromPages?: PdfPageSelection | undefined;
			/**
			 * @default {@link ImageType.PNG}
			 */
			type?: ImageType | undefined;
		} | undefined
	): Promise<string[]> {
		const images = await rasterizeToImageBuffers(
			await this.internal_getId(),
			options?.fromPages ?? "all"
		);

		const promises = images.map(async (imageBuffer, index) => {
			const outputFileName = getFileName(filePath,index.toString(),getImageExtType(options?.type),"image");
			await Jimp.read(imageBuffer).then((value) => {
				return value.write(outputFileName);
			});
			return outputFileName;
		});

		return await Promise.all(promises);
	}

	//#endregion

	//#region text
	/**
	 * Replace the specified old text with new text on a given page
	 * @param oldText Old text to remove
	 * @param newText New text to add
	 * @param onPages Page index to search for old text to replace (default "all")
	 */
	private async internal_replaceText(
		oldText: string,
		newText: string,
		onPages?: PdfPageSelection | undefined
	): Promise<void> {
		return replaceText(await this.internal_getId(), oldText, newText, onPages);
	}

	private async internal_extractText(
		onPages?: PdfPageSelection | undefined
	): Promise<string> {
		return extractAllText(await this.internal_getId(), onPages);
	}

	//#endregion

	//#region pdfA
	/**
	 * Convert the current document into the specified PDF-A standard format
	 * @param pdfaVersion The PDF/A version to convert to
	 * @param customICC (Optional) Custom color profile file path
	 */
	private async internal_convertToPdfA(pdfaVersion: PdfAVersions, customICC?: string | undefined): Promise<void> {
		return await toPdfA(await this.internal_getId(), pdfaVersion, customICC);
	}

	/**
	 * Convert the current document into the specified PDF/UA standard format
	 */
	private async internal_convertToPdfUA(naturalLanguages: NaturalLanguages, pdfUaVersion: PdfUAVersions = PdfUAVersions.PdfUA1): Promise<void> {
		return await toPdfUA(await this.internal_getId(), naturalLanguages, pdfUaVersion);
	}

	//#endregion

	//#region metadata
	/**
	 * Gets a Map<string, string> of metadata properties
	 */
	private async internal_getMetadata(): Promise<Map<string, string>> {
		return getMetadataDict(await this.internal_getId());
	}

	/**
	 * Add or Update a single metadata property
	 * @param key
	 * @param value
	 */
	private async internal_addOrUpdateMetadata(
		key: string,
		value: string
	): Promise<void> {
		return setMetadata(await this.internal_getId(), key, value);
	}

	/**
	 * Remove a single metadata property
	 * @param key
	 */
	private async internal_removeMetadata(key: string): Promise<void> {
		return removeMetadata(await this.internal_getId(), key);
	}


	/**
	 * Sets a whole metadata properties Map<string, string> (override all the metadata property)
	 * @param newMetadataDictionary new metadata properties Map<string, string>
	 */
	private async internal_overrideMetadata(
		newMetadataDictionary: Map<string, string>
	): Promise<void> {
		return setMetadataDict(await this.internal_getId(), newMetadataDictionary);
	}

	//#endregion

	//#region signing
	/**
	 * Sign PDF with digital signature certificate.
	 * Note that the PDF will not be fully signed until Saved
	 * using {@link saveAs} or {@link saveAsBuffer}
	 *
	 * Multiple certificates may be used.
	 * @param signature see {@link DigitalSignature}
	 */
	private async internal_signDigitalSignature(signature: DigitalSignature) {
		return await signPdf(await this.internal_getId(), signature);
	}

	/**
	 * Check if PdfDocument was signed or not
	 */
	private async internal_isSigned(): Promise<boolean> {
		const signatureCount = await getSignatureCount(await this.internal_getId());
		return signatureCount > 0;
	}

	/**
	 * Count the number signature that signed to this PdfDocument
	 */
	private async internal_signatureCount(): Promise<number> {
		return await getSignatureCount(await this.internal_getId());
	}

	/**
	 * Get all verified digital signatures from this PdfDocument.
	 */
	private async internal_getVerifiedSignatures(): Promise<VerifiedSignature[]> {
		return await getVerifiedSignatures(await this.internal_getId());
	}

	//#endregion

	//#region header/footer (affix)
	/**
	 * Apply page header on top of an existing Pdf.
	 * @param header {@link TextAffix}
	 * @param toPages {@link PdfPageSelection}
	 */
	private async internal_addTextHeader(
		header: TextAffix,
		toPages?: PdfPageSelection | undefined
	): Promise<void> {
		await addTextAffix(await this.internal_getId(), toPages, header, true);
	}

	/**
	 * Apply page footer on top of an existing Pdf.
	 * @param footer {@link TextAffix}
	 * @param toPages {@link PdfPageSelection}
	 */
	private async internal_addTextFooter(
		footer: TextAffix,
		toPages?: PdfPageSelection | undefined
	): Promise<void> {
		await addTextAffix(await this.internal_getId(), toPages, footer, false);
	}

	/**
	 * Apply HTML header on top of an existing Pdf.
	 * @param header {@link HtmlAffix}
	 * @param toPages {@link PdfPageSelection}
	 */
	private async internal_addHtmlHeader(
		header: HtmlAffix,
		toPages?: PdfPageSelection | undefined
	): Promise<void> {
		await addHtmlAffix(await this.internal_getId(), toPages, header, true);
	}

	/**
	 * Apply HTML footer on top of an existing Pdf.
	 * @param footer {@link HtmlAffix}
	 * @param toPages {@link PdfPageSelection}
	 */
	private async internal_addHtmlFooter(
		footer: HtmlAffix,
		toPages?: PdfPageSelection | undefined
	): Promise<void> {
		await addHtmlAffix(await this.internal_getId(), toPages, footer, false);
	}

	//#endregion

	//#region stamp
	/**
	 * Edits the PDF by applying the HTML's rendered to only selected page(s).
	 * @param htmlStringOrHtmlFilePath
	 * @param options including {@link HtmlStampOptions} {@link PdfPageSelection}
	 */
	private async internal_stampHtml(
		htmlStringOrHtmlFilePath: HtmlFilePath | HtmlString,
		options?: {
			htmlStampOptions?: HtmlStampOptions | undefined;
			toPages?: PdfPageSelection | undefined;
		} | undefined
	) {
		const html =
			htmlStringOrHtmlFilePath.endsWith(".html") ||
			htmlStringOrHtmlFilePath.endsWith(".htm")
				? fs.readFileSync(htmlStringOrHtmlFilePath).toString()
				: htmlStringOrHtmlFilePath;

		await stampHtml(await this.internal_getId(), html, {
			password: this.pdfPassword,
			htmlStampOptions: options?.htmlStampOptions,
			pageSelection: options?.toPages,
		});
	}

	/**
	 * Edits the PDF by applying the image to only selected page(s).
	 * @param image image file path or image buffer
	 * @param options including {@link ImageStampOptions} {@link PdfPageSelection}
	 */
	private async internal_stampImage(
		image: ImageFilePath | ImageBuffer,
		options?: {
			imageStampOptions?: ImageStampOptions | undefined;
			toPages?: PdfPageSelection | undefined;
		} | undefined
	) {
		const imageBuffer =
			image instanceof Buffer ? image : fs.readFileSync(image);

		await stampImage(await this.internal_getId(), imageBuffer, {
			password: this.pdfPassword,
			imageStampOptions: options?.imageStampOptions,
			pageSelection: options?.toPages,
		});
	}

	/**
	 * Edits the PDF by applying the text to only selected page(s).
	 * @param text text to stamp
	 * @param options including {@link TextStampOptions} {@link PdfPageSelection}
	 */
	private async internal_stampText(
		text: string,
		options?: {
			textStampOptions?: TextStampOptions | undefined;
			toPages?: PdfPageSelection | undefined;
		} | undefined
	) {
		await stampText(await this.internal_getId(), text, {
			password: this.pdfPassword,
			textStampOptions: options?.textStampOptions,
			pageSelection: options?.toPages,
		});
	}

	/**
	 * Edits the PDF by applying the barcode to only selected page(s).
	 * @param barcodeValue barcode
	 * @param options including {@link BarcodeType} {@link BarcodeStampOptions} {@link PdfPageSelection}
	 */
	private async internal_stampBarcode(
		barcodeValue: string,
		options?: {
			barcodeEncoding: BarcodeType;
			barcodeStampOptions?: BarcodeStampOptions | undefined;
			toPages?: PdfPageSelection | undefined;
		} | undefined
	) {
		await stampBarcode(
			await this.internal_getId(),
			barcodeValue,
			{
				password: this.pdfPassword,
				barcodeStampOptions: options?.barcodeStampOptions,
				pageSelection: options?.toPages,
			}
		);
	}

	//#endregion

	//#region background/foreground
	/**
	 * Adds a background to each page of this PDF. The background is copied from a first page in the
	 * backgroundPdf document.
	 *
	 * @param fromPdf background PDF document
	 * @param sourcePageIndex Index (zero-based page number) of the page to copy from the Background/Foreground PDF. Default is 0.
	 * @param applyToPages  PageSelection to which the background/foreground will be added. Default is "all"
	 */
	private async internal_addBackgroundFromAnotherPdf(
		fromPdf: PdfDocument,
		sourcePageIndex = 0,
		applyToPages?: PdfPageSelection | undefined
	): Promise<void> {
		return await addBackgroundForeground(
			await this.internal_getId(),
			await fromPdf.internal_getId(),
			sourcePageIndex,
			true,
			applyToPages
		);
	}

	/**
	 * Adds a foreground to each page of this PDF. The background is copied from a first page in the
	 * backgroundPdf document.
	 *
	 * @param fromPdf foreground PDF document
	 * @param sourcePageIndex Index (zero-based page number) of the page to copy from the Background/Foreground PDF. Default is 0.
	 * @param applyToPages  PageSelection to which the background/foreground will be added. Default is "all"
	 */
	private async internal_addForegroundFromAnotherPdf(
		fromPdf: PdfDocument,
		sourcePageIndex = 0,
		applyToPages?: PdfPageSelection | undefined
	): Promise<void> {
		return await addBackgroundForeground(
			await this.internal_getId(),
			await fromPdf.internal_getId(),
			sourcePageIndex,
			false,
			applyToPages
		);
	}

	//#endregion

	//#region security
	/**
	 * Removes all user and owner password security for a PDF document.  Also disables content
	 * encryption.
	 * If content is encrypted at 128 bit, copy and paste of content, annotations and form editing may be disabled.
	 */
	private async internal_removePasswordsAndEncryption(): Promise<void> {
		await removePasswordsAndEncryption(await this.internal_getId());
	}

	/**
	 * Sets the user password and enables 128Bit encryption of PDF content.
	 * A user password is a password that each user must enter to open or print the PDF document.
	 */
	private async internal_setUserPassword(userPassword: string): Promise<void> {
		if (!this.pdfPassword)
			this.pdfPassword = {userPassword: userPassword};
		else this.pdfPassword.userPassword = userPassword;
		this.pdfDocumentId = await setUserPasswords(
			await this.internal_getId(),
			userPassword
		);
	}

	/**
	 * Sets the owner password and enables 128Bit encryption of PDF content. An owner password is one used to
	 * enable and disable all other security settings. <para>OwnerPassword must be set to a non-empty string
	 * value for {@link PdfPermission.AllowAccessibilityExtractContent} ,  {@link PdfPermission.AllowAnnotations} ,
	 * {@link PdfPermission.AllowFillForms},  {@link PdfPermission.AllowPrint},  {@link PdfPermission.AllowModify} to be
	 * restricted.
	 */
	private async internal_setOwnerPassword(ownerPassword: string): Promise<void> {
		if (!this.pdfPassword)
			this.pdfPassword = {ownerPassword: ownerPassword};
		else this.pdfPassword.ownerPassword = ownerPassword;
		this.pdfDocumentId = await setOwnerPasswords(
			await this.internal_getId(),
			ownerPassword
		);
	}

	/**
	 * Sets the permissions of this PdfDocument
	 * @param permissions see {@link PdfPermission}
	 */
	private async internal_setPermission(permissions: PdfPermission): Promise<void> {
		this.pdfDocumentId = await setSecurity(await this.internal_getId(), permissions);
	}

	/**
	 * Gets the current permissions of this PdfDocument
	 * @return {@link PdfPermission}
	 */
	private async internal_getPermission(): Promise<PdfPermission> {
		return await getPermission(await this.internal_getId());
	}

	/**
	 * Makes this PDF document read only such that: Content is encrypted at 128 bit. Copy and paste of
	 * content is disallowed. Annotations and form editing are disabled.
	 * @param ownerPassword The owner password for the PDF. A string for owner password is required to enable PDF encryption and
	 * all document security options.
	 */
	private async internal_makePdfDocumentReadOnly(ownerPassword: string): Promise<void> {
		await this.internal_setOwnerPassword(ownerPassword);
		await this.internal_setPermission({
			AllowExtractContent: false,
			AllowAccessibilityExtractContent: false,
			AllowAnnotations: false,
			AllowModify: false,
			AllowAssembleDocument: false,
			AllowFillForms: false,
		});
	}

	//#endregion

	//#endregion
}
