import type XRefEntry from './XRefEntry';
import Primitive from './Primitive.svelte';
import type { PathSelectedEvent } from '../events/PathSelectedEvent';
import type { PrimitiveModel } from './PrimitiveModel';
import { StreamData } from './StreamData.svelte';
import { ForgeNotification } from './ForgeNotification.svelte';
import { Mutex } from 'async-mutex';
import TreeViewState from './TreeViewState.svelte';
import PathHistory from './PathHistory.svelte';
import DocumentWorker from './Document.svelte.js';
import { v4 as uuidv4 } from 'uuid';

export default class FileViewState {
	public pathHistory: PathHistory = new PathHistory();

	public fileId: string;
	public fileName: string | undefined = $state();
	public filePath: string | undefined = $state();

	public currentPageNumber: number | undefined = $state();

	public treeMode: boolean = $state(true);
	public pageMode: boolean = $state(false);
	public xRefShowing: boolean = $state(false);
	public notificationsShowing: boolean = $state(false);
	public changesShowing: boolean = $state(false);
	public canForward: boolean = $derived(this.pathHistory.canForward);
	public canBack: boolean = $derived(this.pathHistory.canBack);

	public path: string[] = $state(['Trailer']);
	public container_prim: Primitive | undefined = $state();
	public selected_leaf_prim: Primitive | undefined = $state();
	public xref_entries: XRefEntry[] = $state([]);

	public treeState: TreeViewState | undefined = $state();

	public notifications: ForgeNotification[] = $state([]);
	notificationMutex = new Mutex();

	public changes: PrimitiveModel[] = $state([]);

	public document: DocumentWorker;

	constructor(path: string | undefined, document: DocumentWorker) {
		this.fileId = uuidv4();
		this.notificationMutex = new Mutex();
		this.document = document;
		this.filePath = path;
		this.loadNameFromMetadata();
		this.loadTreeState();
		this.loadXrefEntries();
		this.selectPath(this.path);
	}

	static async load(data: string | ArrayBuffer, path: string | undefined) {
		const document = await DocumentWorker.load(data);
		return new FileViewState(path, document);
	}

	async loadTreeState() {
		this.treeState = new TreeViewState(this.document);
	}

	getLastJump(): string | number | undefined {
		return this.container_prim?.getLastJump();
	}

	getFirstJump(): string | number | undefined {
		return this.container_prim?.getFirstJump();
	}

	public getFileName(): string {
		return this.fileName ? this.fileName : this.getFilePath();
	}

	public getFilePath(): string {
		return this.filePath ? this.filePath : 'Unknown';
	}

	public saveStreamData(updated_data: string) {
		if (!this.selected_leaf_prim) return;
		if (!this.container_prim?.stream_data) return;
		console.log('Saving stream data', updated_data);
		// invoke("update_stream_data_as_string", {
		//     id: this.file.id,
		//     path: this.formatPaths(this.path),
		//     newData: updated_data
		// })
		//     .then(() => {
		//         this.reload();
		//         this.container_prim?.stream_data?.setData(updated_data);
		//     })
		//     .catch(err => this.logError(err));
	}

	public async reload() {
		this.container_prim = undefined;
		this.selected_leaf_prim = undefined;

		this.loadXrefEntries();
		this.treeState?.reload();
		await this.selectPath(this.path);
	}

	public logError(message: string) {
		this.notificationMutex.acquire().then((release) => {
			try {
				console.error(message);
				this.notifications.push(new ForgeNotification(Date.now(), 'ERROR', message));
			} finally {
				release();
			}
		});
	}

	public async deleteNotification(timestamp: number) {
		const release = await this.notificationMutex.acquire();
		try {
			this.notifications = this.notifications.filter((n) => n.timestamp != timestamp);
		} finally {
			release();
		}
	}

	public async clearNotifications() {
		const release = await this.notificationMutex.acquire();
		try {
			this.notifications = [];
		} finally {
			release();
		}
	}

	public async clearChanges() {
		// invoke<PrimitiveModel[]>("clear_changes", {id: this.file.id})
		//     .then(_ => {
		//         this.reload()
		//     })
		//     .catch(err => this.logError(err));
	}

	public loadXrefEntries() {
		this.document.getXrefEntries().then((xrefEntries) => {
			this.xref_entries = xrefEntries.entries;
		});
	}

	public async selectPath(newPath: string[]) {
		if (this.container_prim?.pathEquals(newPath)) {
			return;
		}
		let loadStreamData = false;
		if (newPath.at(-1) === 'Data') {
			newPath = newPath.slice(0, newPath.length - 1);
			loadStreamData = true;
		}
		try {
			const result = await this.document.getPrimByPath(this.formatPaths(newPath));
			if (!result) return;

			const newPrim = new Primitive(result);

			if (newPrim.isContainer()) {
				this.container_prim = newPrim;
				this.currentPageNumber = this.getCurrentPageNumber()
				this.path = newPath;
				this.pathHistory.newPath(newPath);
				if (loadStreamData || this.container_prim.ptype === 'Stream') {
					this.loadStreamData(this.container_prim).catch((err) => this.logError(err));
				}
				return;
			}
			this.selected_leaf_prim = newPrim;
			await this.selectPath(newPath.slice(0, newPath.length - 1)).catch((err) =>
				this.logError(err)
			);
		} catch (e) {
			// @ts-expect-error idk why
			this.logError(e);
		}
	}

	public async loadStreamData(container: Primitive | undefined) {
		if (!container) return;
		const path = this.formatPaths(this.path) + '/Data';
		container.stream_data = new StreamData(container.sub_type);
		if (container.sub_type === 'Image') {
			const data = await this.document.getImageAsUrl(path);
			if (!data) return;
			container.stream_data.setImageData(data);
		} else {
			const data = await this.document.getStreamAsString(path);
			if (!data) return;
			container.stream_data.setData(data);
		}
	}

	public dispose() {
		this.document.dispose();
	}

	public selectPathHandler(event: PathSelectedEvent) {
		if (event.detail.file_id !== this.fileId) {
			return;
		}
		this.selectPath(event.detail.path);
	}

	public getMergedPath() {
		return this.formatPaths(this.path);
	}

	public back() {
		if (!this.canBack) return;
		const prevPath = this.pathHistory.goBack();
		if (prevPath) {
			this.selectPath(prevPath);
		}
	}

	public forward() {
		if (!this.canForward) return;
		const prevPath = this.pathHistory.goForward();
		if (prevPath) {
			this.selectPath(prevPath);
		}
	}

	public jump(i: number) {
		const prevPath = this.pathHistory.jump(i);
		if (prevPath) {
			this.selectPath(prevPath);
		}
	}

	public save() {
		console.log("saving")
		const savePromise = this.document.save();
		savePromise.then((data: Uint8Array) => {
			const blob = new Blob([data], { type: 'text/plain' });
			const url = URL.createObjectURL(blob);

			const a = document.createElement('a');
			a.href = url;
			a.download = this.filePath ? this.filePath : "document.pdf";
			a.click();

			URL.revokeObjectURL(url);
		}).catch((error: unknown) => {
			console.error('Save failed:', error);
		});
	}

	public copyPath() {
		const _path: string[] = [];

		for (const item of this.path) {
			_path.push(item);
		}
		return _path;
	}

	public selectXref(entry: XRefEntry | undefined) {
		if (!entry) {
			this.selectPath(['/']);
			return;
		}
		this.selectPath([entry.obj_num.toString()]);
	}

	public formatPaths(paths: string[]) {
		if (paths.length == 0) {
			return '/';
		}
		if (paths[0] === '/') {
			return 'Trailer/' + paths.slice(1, paths.length).join('/');
		}
		return paths.join('/');
	}

	public getCurrentPageNumber(): number | undefined {
		if (!(this.container_prim && this.container_prim.isPage())) return undefined;
		return this.container_prim.getPageNumber();
	}

	private async loadNameFromMetadata() {
		this.fileName = await this.document.getTitle();
	}
}
