import {Whiteboard, WhiteboardApplication, WhiteboardPermissionFlag} from "@netless/forge-whiteboard";
import {ISlideConfig, Slide, SLIDE_EVENTS} from "@netless/slide";
import {AbstractApplication} from "@netless/forge-room";
import {SlideForge} from "./Slide";
import {ForgeSlidePermissionFlag, ForgeSlidePermissions} from "./ForgeSlidePermession";
import {FooterView} from "./FoorerView";
import * as Y from "yjs";
import {SideBarView} from "./SiderBarView";
import {deepEqual, delay} from "./utils";
import { kvStore } from "@netless/forge-room";

export interface SlideApplicationOption {
    prefix: string;
    taskId: string;
    options?: ISlideConfig;
    /**
     * 从指定的白板 id 继承白板权限以及工具栏配置
     */
    inheritWhiteboardId?: string;
}

interface PreviewImage {
    url: string;
    src: string;
    width: number;
    height: number;
}

export const Slide_APP_NAME = "forge_slide";

export class SlideApplication extends AbstractApplication<SlideApplicationOption, SlideForge> {

    static applicationName = Slide_APP_NAME;

    public readonly name: string = Slide_APP_NAME;
    public readonly emitter: SlideForge = new SlideForge();

    private whiteboardApp!: WhiteboardApplication;
    private whiteboard!: Whiteboard;
    private rootView: HTMLDivElement = document.createElement("div");
    private contentContainer: HTMLDivElement = document.createElement("div");
    private whiteboardContainer: HTMLDivElement = document.createElement("div");
    private slideContainer: HTMLDivElement = document.createElement("div");
    private permissions!: ForgeSlidePermissions;
    private footer: FooterView;
    private sideBar: SideBarView;
    private slide!: Slide;
    private currentSlideIndex: number = 0;
    private taskId: string = "";
    private prefix: string = "";
    private slideCount: number = 0;

    constructor() {
        super();
        (window as any).emitter = this.emitter;
        this.rootView.setAttribute("data-forge-app", Slide_APP_NAME);
        this.rootView.style.background = "#f0f0f0f0";
        this.rootView.style.overflow = "hidden";

        this.contentContainer.style.width = "100%";
        this.contentContainer.style.height = "100%";
        this.contentContainer.style.display = "flex";
        this.contentContainer.style.flexDirection = "column";

        this.slideContainer.style.width = "100%";
        this.slideContainer.style.height = "100%";
        // this.slideContainer.style.height = "calc(100% - 24px)";

        this.footer = new FooterView();
        this.sideBar = new SideBarView();
        this.sideBar.on("pageChange", index => {
            if (index > 0 && index <= this.slideCount) {
                this.slide.renderSlide(index);
            }
        });
        this.footer.on("prevStep", () => {
            if (!this.permissions.hasPermission(ForgeSlidePermissionFlag.changeStep)) {
                return;
            }
            if (this.slide.mainSeqStep > 0 || this.slide.mainSeqState !== "idle") {
                this.slide.prevStep();
            } else if (this.currentSlideIndex > 1){
                if (!this.permissions.hasPermission(ForgeSlidePermissionFlag.changePage)) {
                    return;
                }
                this.slide.renderSlide(this.currentSlideIndex - 1)
            }
        });
        this.footer.on("nextStep", () => {
            if (!this.permissions.hasPermission(ForgeSlidePermissionFlag.changeStep)) {
                return;
            }
            if (this.slide.mainSeqStep < this.slide.mainSeqLength) {
                this.slide.nextStep()
            } else {
                if (!this.permissions.hasPermission(ForgeSlidePermissionFlag.changePage)) {
                    return;
                }
                this.slide.renderSlide(this.currentSlideIndex + 1)
            }
        });
        this.footer.on("prevPage", () => {
            if (!this.permissions.hasPermission(ForgeSlidePermissionFlag.changePage)) {
                return;
            }
            if (this.currentSlideIndex > 1) {
                this.slide.renderSlide(this.currentSlideIndex - 1);
            }
        });
        this.footer.on("nextPage", () => {
            if (!this.permissions.hasPermission(ForgeSlidePermissionFlag.changePage)) {
                return;
            }
            if (this.currentSlideIndex < this.slideCount) {
                this.slide.renderSlide(this.currentSlideIndex + 1);
            }
        });

        this.footer.on("sideBarToggle", () => {
            if (!this.permissions.hasPermission(ForgeSlidePermissionFlag.changePage)) {
                return;
            }
            if (this.sideBar.root.style.left === "0px") {
                this.sideBar.root.style.left = "-240px";
            } else {
                this.sideBar.root.style.left = "0px";
            }
        })

        this.rootView.appendChild(this.contentContainer);

        this.emitter.on("renderStart", pageIndex => {
            this.footer.prevPageState(pageIndex !== 0);
        });

        const that = this;
        Object.defineProperty(this.emitter, "view", {
            get(): any {
                return that.rootView;
            }
        });
        Object.defineProperty(this.emitter, "permissions", {
            get(): any {
                return that.permissions;
            }
        });
        Object.defineProperty(this.emitter, "footView", {
            get(): any {
                return that.footer.root;
            }
        })
        Object.defineProperty(this.emitter, "pageIndex", {
            get(): any {
                return that.currentSlideIndex;
            }
        });
        Object.defineProperty(this.emitter, "pageCount", {
            get(): any {
                return that.slideCount;
            }
        });
        Object.defineProperty(this.emitter, "goto", {
            writable: false,
            enumerable: false,
            value: (pageIndex: number) => {
                this.sideBar.emit("pageChange", pageIndex);
            }
        });
        Object.defineProperty(this.emitter, "nextStep", {
            writable: false,
            enumerable: false,
            value: () => {
                this.footer.emit("nextStep");
            }
        })
        Object.defineProperty(this.emitter, "prevStep", {
            writable: false,
            enumerable: false,
            value: () => {
                this.footer.emit("prevStep");
            }
        })
        Object.defineProperty(this.emitter, "nextPage", {
            writable: false,
            enumerable: false,
            value: () => {
                this.footer.emit("nextPage");
            }
        })
        Object.defineProperty(this.emitter, "prevPage", {
            writable: false,
            enumerable: false,
            value: () => {
                this.footer.emit("prevPage");
            }
        })
        Object.defineProperty(this.emitter, "sideBarToggle", {
            writable: false,
            enumerable: false,
            value: () => {
                this.footer.emit("sideBarToggle");
            }
        })
        Object.defineProperty(this.emitter, "imgContent", {
            writable: false,
            enumerable: false,
            value: (pageIndex: number) => {
                return this.getImageContent(pageIndex);
            }
        })

        Object.defineProperty(this.emitter, "imgUrl", {
            writable: false,
            enumerable: false,
            value: (pageIndex: number) => {
                return this.getImageUrl(pageIndex);
            }
        })

        Object.defineProperty(this.emitter, "imgSize", {
            writable: false,
            enumerable: false,
            value: (pageIndex: number) => {
                return this.getImageSize(pageIndex);
            }
        })
    }

    private async getPreviewImage(imageUrl: string) {
        const image = fetch(imageUrl);
        return await image.then(res => res.blob()).then(blob => {
            return new Promise<PreviewImage>(resolve => {
                const reader = new FileReader();
                reader.onloadend = () => {
                    const base64Data = reader.result as string;
                    const img = document.createElement("img");
                    img.src = base64Data;
                    img.onload = () => {
                        resolve({
                            url: imageUrl,
                            src: base64Data,
                            width: img.width,
                            height: img.height,
                        });
                    }
                }
                reader.readAsDataURL(blob);
            })
        });
    }

    private async getImageUrl(pageIndex: number) {
        return `${this.prefix}/${this.taskId}/preview/${pageIndex + 1}.png`;
    }

    private async getImageSize(pageIndex: number) {
        const imageUrl = `${this.prefix}/${this.taskId}/preview/${pageIndex + 1}.png`;
        let preview: PreviewImage | null = null;
        try {
            const result = await kvStore.getItem(imageUrl);
            preview = result ? JSON.parse(result) : null;
        } catch (e) {
            console.warn("kvStore getItem error", e);
        }
        if (preview) {
            return { width: preview.width, height: preview.height };
        }
        preview = await this.getPreviewImage(imageUrl);
        await kvStore.setItem(imageUrl, JSON.stringify(preview));
        return { width: preview.width, height: preview.height };
    }

    private async getImageContent(pageIndex: number) {
        const imageUrl = `${this.prefix}/${this.taskId}/preview/${pageIndex + 1}.png`;
        let preview: PreviewImage | null = null;
        try {
            const result = await kvStore.getItem(imageUrl);
            preview = result ? JSON.parse(result) : null;
        } catch (e) {
            console.warn("kvStore getItem error", e);
        }
        if (preview) {
            return preview.src;
        }
        preview = await this.getPreviewImage(imageUrl);
        await kvStore.setItem(imageUrl, JSON.stringify(preview));
        return preview.src;
    }

    private applySlideState = async (slideState: any, lastDispatch: any) => {
        if (this.slide.slideState.currentSlideIndex < 0) {
            this.slide.emit(SLIDE_EVENTS.syncReceive, lastDispatch);
            return;
        }
        if (this.slide.slideState.currentSlideIndex > 0 && deepEqual(this.slide.slideState, slideState)) {
            this.slide.emit(SLIDE_EVENTS.syncReceive, lastDispatch);
        } else {
            await this.slide.setSlideState(slideState);
            await delay(200);
            this.slide.emit(SLIDE_EVENTS.syncReceive, lastDispatch);
        }
    }

    private onSlideEventHandler = async (event: Y.YMapEvent<any>) => {
        for (const [key, value] of event.changes.keys.entries()) {
            if (key === "syncSlide") {
                if (value.action === "add" || value.action === "update") {
                    const { slideState: slideStateFromServer, dispatch: dispatchFromServer } = this.getMap(this.name).get("syncSlide");
                    this.applySlideState(slideStateFromServer, dispatchFromServer);
                }
            }
        }
    }

    public async initialize(option: SlideApplicationOption): Promise<void> {
        this.prefix = option.prefix;
        this.taskId = option.taskId;
        const whiteboardApp = new WhiteboardApplication();
        // @ts-ignore
        whiteboardApp.roomDoc = this.roomDoc;
        // @ts-ignore
        whiteboardApp.appId = `${option.taskId}_wb`;
        // @ts-ignore
        whiteboardApp.userId = this.userId;
        // @ts-ignore
        whiteboardApp.userManager = this.userManager;
        // @ts-ignore
        whiteboardApp.applicationManager = this.applicationManager;

        const json = await fetch(`${option.prefix}/${option.taskId}/jsonOutput/slide-1.json`).then(res => res.json())
        this.slideCount = json.slideCount;
        await whiteboardApp.initialize({
            width: json.width,
            height: json.height,
        });
        this.whiteboardApp = whiteboardApp;
        this.whiteboard = whiteboardApp.emitter;
        this.whiteboard.enableCameraByMouse = false;
        this.whiteboard.enableCameraByTouch = false;
        this.whiteboard.view.style.width = "100%";
        this.whiteboard.view.style.height = "100%";
        this.whiteboard.view.style.position = "absolute";
        this.whiteboard.view.style.top = "0";
        this.whiteboard.view.style.left = "0";
        this.whiteboard.view.style.zIndex = "4";
        this.whiteboard.permissions.addPermission(WhiteboardPermissionFlag.all);
        this.whiteboard.setCanvasBackgroundColor("#f0f0f000");
        this.whiteboardContainer.style.position = "relative";
        this.whiteboardContainer.style.flex = "0 0 auto";
        this.whiteboardContainer.style.height = "calc(100% - 24px)";
        this.whiteboardContainer.appendChild(this.whiteboard.view);
        this.whiteboardContainer.appendChild(this.slideContainer);

        this.contentContainer.appendChild(this.whiteboardContainer);
        this.contentContainer.appendChild(this.footer.root);
        this.contentContainer.appendChild(this.sideBar.root);

        if (option.inheritWhiteboardId) {
            whiteboardApp.linkToWhiteboard(option.inheritWhiteboardId);
        }

        this.whiteboard.setViewModeToMain();


        this.slideContainer.setAttribute("builder", "slide-builder")
        this.slide = new Slide({
            ...option.options,
            interactive: true,
            anchor: this.slideContainer,
            mode: "interactive",
            // logger: {
            //     info: console.log,
            //     warn: console.warn,
            //     error: console.error,
            // },
        })
        this.slide.setResource(option.taskId, option.prefix);
        this.sideBar.initialize(json.slideCount, option);

        this.slide.on("syncDispatch", event => {
            this.getMap(this.name).set("syncSlide", {
                slideState: this.slide.slideState,
                dispatch: event,
            })
        })

        this.slide.on("mainSeqStepStart", (animateIndex: number) => {
            this.emitter.emit("mainSeqStepStart", animateIndex);
        })

        this.slide.on("mainSeqStepEnd", (animateIndex: number) => {
            this.emitter.emit("mainSeqStepEnd", animateIndex);
        })

        this.slide.on("renderStart", (slideIndex) => {
            this.whiteboardApp.emitter.view.style.opacity = "0";
            this.whiteboardApp.emitter.addPage(`${slideIndex}`);
            this.whiteboardApp.emitter.gotoPage(`${slideIndex}`);
            this.emitter.emit("renderStart", slideIndex);
        })

        this.slide.on("renderEnd", (slideIndex) => {
            this.currentSlideIndex = slideIndex;
            this.whiteboardApp.emitter.view.style.opacity = "1";
            this.emitter.emit("renderEnd", slideIndex);
        });

       this.slide.on("stateChange", (state) => {
           this.getMap(this.name).set("slideState", state)
       })

        this.getMap(this.name).observe(this.onSlideEventHandler);

        (window as any).slide = this.slide;
        (window as any).slideWhiteboard = this.whiteboardApp;
        (window as any).forgeSlide = this;

        const syncSlide = this.getMap(this.name).get("slideState");
        if (syncSlide && syncSlide.taskId === option.taskId) {
            this.slide.setSlideState(syncSlide)
        } else {
            this.slide.renderSlide(1);
        }
        this.permissions = new ForgeSlidePermissions(this.userManager, (userId: string) => {
            return this.userMap(userId);
        });
        this.permissions.on("change", (userId, flags, value) => {
            this.emitter.emit("permissionChange", userId, flags, value);
            if (this.userId === userId) {
                if (flags.includes(ForgeSlidePermissionFlag.clickAnim)) {
                    this.slideContainer.style.pointerEvents = "auto";
                } else {
                    this.slideContainer.style.pointerEvents = "none";
                }
            }
        });
        this.permissions.addPermission(ForgeSlidePermissionFlag.all);
        this.whiteboardApp.disableViewModel();
        // @ts-ignore
        window.__forge_slide = this;
        // @ts-ignore
        window.slidePermissions = this.permissions;
        return Promise.resolve(undefined);
    }

    private userMap(userId: string): Y.Map<any> {
        return this.getMap(`user/${userId}`);
    }

    public async dispose(): Promise<void> {
        await this.whiteboardApp.dispose();
        this.rootView.parentElement?.removeChild(this.rootView);
        this.slide.destroy();
        this.sideBar.dispose();
        this.getMap(this.name).unobserve(this.onSlideEventHandler)
        return Promise.resolve(undefined);
    }

}