import type PdfFile from "./PdfFile";
import type XRefEntry from "./XRefEntry";
import Primitive from "./Primitive.svelte";
import {invoke} from "@tauri-apps/api/core";
import type XRefTable from "./XRefTable";
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 {RawImageData} from "./RawImageData";
import TreeViewState from "./TreeViewState.svelte";
import {PageViewState} from "./PageViewState.svelte";
import PathHistory from "./PathHistory.svelte";
import Document from "./Document.svelte.js";

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

    public file: PdfFile;

    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 pageViewState: PageViewState | undefined = $state();

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

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

    public renderer: Document;


    constructor(file: PdfFile) {

        this.file = file;
        this.selectPath(this.path);
        this.loadXrefEntries()
        this.notificationMutex = new Mutex();
        this.treeState = new TreeViewState(this);
        this.renderer = new Document(file.bytes)
    }

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

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

    public saveStreamData(updated_data: string) {
        if (!this.selected_leaf_prim) return;
        if (!this.container_prim?.stream_data) return;
        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(); // Always release the lock
        }
    }

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

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

    public loadXrefEntries() {
        invoke<XRefTable>("get_xref_table", {id: this.file.id})
            .then(result => {
                this.xref_entries = result.entries;
            })
            .catch(err => this.logError(err));

        invoke<PrimitiveModel[]>("get_changes", {id: this.file.id})
            .then(result => {
                this.changes = result;
            })
            .catch(err => this.logError(err));
    }

    public async selectPath(newPath: string[]) {
        if (this.container_prim?.pathEquals(newPath)) {
            return;
        }
        const result = await invoke<PrimitiveModel>("get_prim_by_path", {
            id: this.file.id,
            path: this.formatPaths(newPath)
        }).catch(err => this.logError(err))
        if (!result) return;

        let newPrim = new Primitive(result)

        if (newPrim.isContainer()) {
            this.container_prim = newPrim;
            this.path = newPath
            this.pathHistory.newPath(newPath);
            this.loadPageState().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));
        await this.loadStreamData(this.container_prim, newPrim,).catch(err => this.logError(err));
    }

    public async loadStreamData(container: Primitive | undefined, leaf: Primitive | undefined) {
        if (!container || !leaf) return;
        if (leaf.ptype === "Stream Data" &&
            leaf.isChildOf(container)
        ) {
            const path = this.formatPaths(this.path) + "/Data";
            container.stream_data = new StreamData(leaf.sub_type)
            if (leaf.sub_type === "Image") {
                const data = await invoke<ArrayBuffer>("get_stream_data_as_image", {id: this.file.id, path: path})
                    .catch(err => this.logError(err))
                if (!data) return;
                container.stream_data.setImageData(new RawImageData(data));
            } else {
                const data = await invoke<string>("get_stream_data_as_string", {id: this.file.id, path: path})
                    .catch(err => this.logError(err))
                if (!data) return;
                container.stream_data.setData(data);
            }
        }
    }


    public selectPathHandler(event: PathSelectedEvent) {

        if (event.detail.file_id !== this.file.id) {
            return;
        }
        this.selectPath(event.detail.path);
    }

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

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

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

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

    public save() {

    }

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

        for (let 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 loadPageState() {

        let page_num = this.getCurrentPageNumber();
        if (page_num) {
            await this.pageViewState?.load(page_num);
        }
    }
}