import PQueue from 'p-queue';
import {FileMetas} from "./model.ts";
import Emittery from "emittery";

export type ImageItemState = "unloaded" | "queueing" | "loading" | "loaded"  |  "error";

export interface ImageItem {
    src: string;
    page: number;
    state: ImageItemState;
    el?: HTMLImageElement;
    width?: number;
    height?: number;
    error?: string;
}

interface ImagePreloaderEvents {
    loaded: ImageItem;
}

export class ImagePreloader extends Emittery<ImagePreloaderEvents> {

    private queue: PQueue;

    private readonly items: ImageItem[];

    constructor(metas: FileMetas) {
        super();
        this.queue = new PQueue({concurrency: 2, autoStart: true});

        const items: ImageItem[] = [];
        if (metas.page && metas.image && metas.image.imageUrlPrefix) {
            for (let i = 1; i <= metas.page; i++) {
                items.push({
                    src: `${metas.image.imageUrlPrefix}${i}`,
                    page: i,
                    state: "unloaded",
                });
            }
        } else if (metas.page && metas.image && metas.image.urls) {
            metas.image.urls.forEach((url, index) => {
                items.push({
                    src: url,
                    page: index + 1,
                    state: "unloaded",
                });
            });
        } else {
            throw new Error("File metas invalid.");
        }
        this.items = items;
    }

    public getItems(): ImageItem[] {
        return this.items;
    }

    public pre(page: number): ImageItem {
        let index = page - 1;
        index = Math.max(0, index);
        index = Math.min(this.items.length, index);
        const item = this.items[index];
        if (item.state !== "unloaded") {
            return item;
        }

        item.state = "queueing";
        this.queue.add(async () => {
            return new Promise<void>(async (resolve, reject) => {
                try {
                    item.state = "loading";
                    const response = await fetch(item.src);
                    const blob = await response.blob();
                    const url = URL.createObjectURL(blob);

                    const img = new Image();
                    item.el = img;
                    img.src = url;
                    img.onload = () => {
                        item.state = "loaded";
                        item.width = img.naturalWidth;
                        item.height = img.naturalHeight;
                        this.emit("loaded", item).then();
                        resolve();
                    }
                    img.onerror = () => {
                        throw new Error("Img error.");
                    }
                } catch (error: unknown) {
                    item.state = "error";
                    this.emit("loaded", item).then();
                    reject(error);
                }
            });
        }).then();

        return item;
    }
}
