import { Buffer } from "buffer";

import path from "path";
import { RemoteExceptionP__Output } from "../generated_proto/ironpdfengineproto/RemoteExceptionP";
import {
	ImageBuffer,
	ImageFilePath,
	PageInfo,
	PdfInput,
	PdfPageSelection,
} from "../../public/types";
import { ImageInput, PdfInputType } from "../internalType";
import { PdfDocument } from "../../public/pdfDocument";
import { PdfDocumentResultP__Output } from "../generated_proto/ironpdfengineproto/PdfDocumentResultP";
import { EmptyResultP__Output } from "../generated_proto/ironpdfengineproto/EmptyResultP";
import { ImageType } from "../../public/image";
import { getPageInfo } from "./pdfium/page";

export function handleRemoteException(
	proto: RemoteExceptionP__Output,
	reject: (errorMsg: string) => void
): void {
	reject(
		`${proto.exceptionType} ${proto.message} \n ${proto.remoteStackTrace} \n ${proto.rootException}`
	);
}

export const CHUNK_SIZE = 65536;

export function chunkString(str: string): Array<string> | null {
	return str.match(/.{1,65536}/g);
}

export function chunkBuffer(buffer: Buffer): Buffer[] {
	const chunks: Buffer[] = [];

	for (let i = 0; i < buffer.length; i += CHUNK_SIZE) {
		const chunk = buffer.subarray(i, i + CHUNK_SIZE);
		chunks.push(chunk);
	}

	return chunks;
}

const urlPattern = new RegExp(
	"^(https?:\\/\\/)?" + // validate protocol
		"((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" + // validate domain name
		"((\\d{1,3}\\.){3}\\d{1,3}))" + // validate OR ip (v4) address
		"(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // validate port and path
		"(\\?[;&a-z\\d%_.~+=-]*)?" + // validate query string
		"(\\#[-a-z\\d_]*)?$",
	"i"
); // validate fragment locator

function isValidUrl(urlString: string) {
	return urlPattern.test(urlString);
}

export function separatePdfInput(input: PdfInput): PdfInputType {
	if (typeof input === "string") {
		if (input.endsWith(".html") || input.endsWith(".htm")) {
			return { type: "htmlFile", htmlFile: input };
		} else if (input.endsWith(".pdf")) {
			return { type: "pdfFile", pdfFile: input };
		} else if (input.endsWith(".zip")) {
			return { type: "zipFile", zipFile: input };
		} else if (input.indexOf("<") > -1) {
			return { type: "htmlString", htmlString: input };
		} else if (isValidUrl(input)) {
			return { type: "url", url: new URL(input) };
		} else {
			return { type: "htmlString", htmlString: input };
		}
	} else if (input instanceof Buffer) {
		return { type: "buffer", buffer: input };
	} else if (input instanceof URL) {
		return { type: "url", url: input };
	} else if (input instanceof PdfDocument)
		return { type: "pdfDocument", pdfDocument: input };
	throw new Error(`unknown input : ${input}`);
}

export function separateImageBufferOrImagePathInput(
	input: ImageFilePath | ImageBuffer
): ImageInput {
	if (typeof input === "string") {
		return { type: "imageFilePath", imageFilePath: input };
	} else if (input instanceof Buffer) {
		return { type: "imageBuffer", imageBuffer: input };
	}
	throw new Error(`unknown input : ${input}`);
}

export function handlePdfDocumentResultP__Output(
	proto: PdfDocumentResultP__Output | undefined,
	resolve: (_: string) => void,
	reject: (errorMsg: string) => void
) {
	if (proto) {
		if (proto?.exception) {
			handleRemoteException(proto.exception, reject);
		}
		if (proto.result) {
			resolve(proto.result.documentId!);
		}
	}
	// throw new Error("Error empty message");
}

export function handleEmptyResultP__Output(
	proto: EmptyResultP__Output | undefined,
	reject: (errorMsg: string) => void
): void {
	if (proto?.exception) {
		handleRemoteException(proto.exception, reject);
	}
}

export async function AsyncPdfPageSelectionToIndexes(
	documentId: string,
	pdfPageSelection?: PdfPageSelection | undefined
): Promise<number[]> {
	const result = pdfPageSelection
		? typeof pdfPageSelection === "number"
			? [pdfPageSelection]
			: Array.isArray(pdfPageSelection)
			? pdfPageSelection
			: []
		: [];

	if (result.length == 0) {
		return Array.from({
			length: (await getPageInfo(documentId)).length,
		}).map((_, index) => index);
	}
	return result;
}

export function PdfPageSelectionToIndexes(
	pagesInfo: PageInfo[],
	pdfPageSelection?: PdfPageSelection | undefined
): number[] {
	const result = pdfPageSelection
		? typeof pdfPageSelection === "number"
			? [pdfPageSelection]
			: Array.isArray(pdfPageSelection)
			? pdfPageSelection
			: []
		: [];

	if (result.length == 0) {
		return Array.from({
			length: pagesInfo.length,
		}).map((_, index) => index);
	}
	return result;
}

export function getFileNames(
	filePath: string,
	fileCount: number,
	specificFileExt?: string | undefined,
	defaultName = "output"
): string[] {
	const directory = path.dirname(filePath);
	const extensionFromString = path.extname(filePath);
	const baseName =
		path.basename(filePath, extensionFromString) ?? defaultName;

	let finalExtension = extensionFromString;
	if (specificFileExt && specificFileExt != extensionFromString) {
		finalExtension = extensionFromString + specificFileExt; // appended e.g. "c:/tmp/aa.jpg" -> "c:/tmp/aa.jpg.png"
	}

	if (fileCount == 1) {
		return [path.join(directory, `${baseName}${finalExtension}`)];
	} else {
		return Array.from({ length: fileCount }, (_, i) => i).map((index) => {
			return path.join(
				directory,
				`${baseName}_${index}${finalExtension}`
			);
		});
	}
}

export function getFileName(
	filePath: string,
	fileSuffix: string,
	specificFileExt?: string | undefined,
	defaultName = "output"
): string {
	const directory = path.dirname(filePath);
	const extensionFromString = path.extname(filePath);
	const baseName =
		path.basename(filePath, extensionFromString) ?? defaultName;

	let finalExtension = extensionFromString;
	if (specificFileExt && specificFileExt != extensionFromString) {
		finalExtension = extensionFromString + specificFileExt; // appended e.g. "c:/tmp/aa.jpg" -> "c:/tmp/aa.jpg.png"
	}

	return path.join(directory, `${baseName}_${fileSuffix}${finalExtension}`);
}

export function getImageExtType(imageType?: ImageType | undefined): string | undefined {
	switch (imageType) {
		case ImageType.JPG:
			return ".jpg";
		case ImageType.BMP:
			return ".bmp";
		case ImageType.PNG:
			return ".png";
		default:
			return undefined;
	}
}

// export function notNullOrEmpty<T>(obj: T | null | undefined): obj is T {
// 	return obj !== undefined && obj !== null;
//
// }

export function isNullOrUndefined<T>(obj: T | null | undefined): obj is null | undefined {
	return obj === undefined || obj === null;
}
