﻿import * as ko from "knockout";
import { EventManager, Events, GlobalEventHandler } from "@paperbits/common/events";
import { IComponent, ViewManager, ViewManagerMode } from "@paperbits/common/ui";
import { Router, Route } from "@paperbits/common/routing";
import { MetaDataSetter } from "@paperbits/common/meta/metaDataSetter";
import { SiteService, SiteSettingsContract } from "@paperbits/common/sites";
import { IMediaService } from "@paperbits/common/media";

interface HostConfig {
    viewport: ko.Observable<string>;
    block: ko.Observable<boolean>;
    host: ko.Observable<IComponent>;
    onDocumentCreated: (document: Document) => void;
    onDocumentDisposed: () => void;
}


export class HostBindingHandler {
    private readonly hostComponent: ko.Observable<any>;
    private readonly designTime: ko.Observable<boolean>;
    private onDocumentCreated: (document: Document) => void;
    private onDocumentDisposed: () => void;

    constructor(
        private readonly globalEventHandler: GlobalEventHandler,
        private readonly viewManager: ViewManager,
        private readonly router: Router,
        private readonly siteService: SiteService,
        private readonly mediaService: IMediaService
    ) {
        this.hostComponent = ko.observable();
        this.designTime = ko.observable(true);

        ko.bindingHandlers["host"] = {
            init: (element: HTMLElement, valueAccessor: () => HostConfig) => {
                const config = valueAccessor();
                const css = ko.observable<string>("desktop");

                config.block.subscribe(this.designTime);

                config.viewport.subscribe((viewport: string) => {
                    this.viewManager.mode = ViewManagerMode.selecting;

                    switch (viewport) {
                        case "zoomout":
                            css("viewport-zoomout");
                            this.viewManager.mode = ViewManagerMode.pause;
                            break;

                        case "xl":
                            css("viewport-xl");
                            break;

                        case "lg":
                            css("viewport-lg");
                            break;

                        case "md":
                            css("viewport-md");
                            break;

                        case "sm":
                            css("viewport-sm");
                            break;

                        case "xs":
                            css("viewport-xs");
                            break;

                        default:
                            throw new Error("Unknown viewport");
                    }
                });

                this.onDocumentCreated = config.onDocumentCreated;
                this.onDocumentDisposed =  config.onDocumentDisposed;

                ko.applyBindingsToNode(element, { css: css }, null);

                const hostElement = this.createIFrame();
                element.appendChild(hostElement);
            },

            update: (element: HTMLElement, valueAccessor: any) => {
                const config = valueAccessor();
                this.hostComponent(config.host());
            }
        };
    }


    private createIFrame(): HTMLIFrameElement {
        const hostElement: HTMLIFrameElement = document.createElement("iframe");
        hostElement.src = "/page.html?designtime=true";
        hostElement.classList.add("host");
        hostElement.title = "Website";
        hostElement.tabIndex = -1;
        hostElement.setAttribute("aria-hidden", "true");

        let hostedWindowHistory;

        const onRouteChange = (route: Route): void => {
            route.metadata.originatedByHost = true;
            hostedWindowHistory.pushState(route, route.title, route.url);
        };

        const onLoad = async (): Promise<void> => {
            const contentDocument = hostElement.contentDocument;

            if (this.onDocumentCreated) {
                this.onDocumentCreated(contentDocument);
            }

            this.globalEventHandler.appendDocument(contentDocument);
            this.setRootElement(contentDocument.body);

            /* intercepting push state of the hosted iframe window */
            hostedWindowHistory = hostElement.contentDocument.defaultView.window.history;
            const hostedWindowOriginalPushState = hostedWindowHistory.pushState;

            /* adding listener to designer's route handler */
            this.router.addRouteChangeListener(onRouteChange);

            /* pushing initial route to route handler of the hosted iframe window */
            const initialRoute = this.router.getCurrentRoute();
            hostedWindowHistory.pushState(initialRoute, initialRoute.title, location.href);

            /* overriding pushState method of the hosted iframe window */
            hostedWindowHistory.pushState = (route: Route, title: string, url: string) => {
                hostedWindowOriginalPushState.call(hostedWindowHistory, route, title, url);

                /* if event originated in the designer, skip pushing the state to avoid circular calls */
                if (route && route.metadata.originatedByHost) {
                    return;
                }

                /* synching the state with designer's window */
                window.history.pushState(route, route.title, route.url);
            };

            const settings = await this.siteService.getSettings<any>();
            const siteSettings: SiteSettingsContract = settings?.site;

            if (!siteSettings?.faviconSourceKey) {
                return;
            }

            const mediaContract = await this.mediaService.getMediaByKey(siteSettings.faviconSourceKey);

            if (!mediaContract || !mediaContract.permalink) {
                return;
            }

            MetaDataSetter.setFavIcon(mediaContract.permalink);
        };

        const onUnload = (): void => {
            /* removing listener when iframe gets unloaded */
            this.router.removeRouteChangeListener(onRouteChange);

            if (this.onDocumentDisposed) {
                this.onDocumentDisposed();
            }
        };

        hostElement.addEventListener("load", onLoad, false);
        hostElement.addEventListener("beforeunload", onUnload, false);

        return hostElement;
    }

    private setRootElement(bodyElement: HTMLElement): void {
        const styleElement = document.createElement("style");
        bodyElement.ownerDocument.head.appendChild(styleElement);

        ko.applyBindingsToNode(bodyElement, { css: { design: this.designTime } }, null);
        ko.applyBindingsToNode(styleElement, { styleSheet: {} }, null);
        ko.applyBindingsToNode(bodyElement, { component: this.hostComponent }, null);
    }
}