import * as Objects from "@paperbits/common/objects";
import { Bag, Contract } from "@paperbits/common";
import { CollectionModelBinder, IWidgetBinding } from "@paperbits/common/editing";
import { EventManager, Events } from "@paperbits/common/events";
import { ViewModelBinder } from "@paperbits/common/widgets";
import { ViewModelBinderSelector } from "../../ko/viewModelBinderSelector";
import { ContentHandlers } from "../contentHandlers";
import { ContentModel } from "../contentModel";
import { ContentModelBinder } from "../contentModelBinder";
import { ContentViewModel } from "./contentViewModel";
import { WidgetViewModel } from "../../ko";
import { ContentPart } from "../../content-part/ko";
import { Placeholder } from "../../placeholder/ko";
import { ComponentFlow } from "@paperbits/common/components";


export class ContentViewModelBinder implements ViewModelBinder<ContentModel, ContentViewModel> {
    constructor(
        private readonly viewModelBinderSelector: ViewModelBinderSelector,
        private readonly contentModelBinder: ContentModelBinder<ContentModel>,
        private readonly containerModelBinder: CollectionModelBinder,
        private readonly eventManager: EventManager
    ) { }

    public createBinding(model: ContentModel, viewModel: ContentViewModel, bindingContext: Bag<any>): void {
        let savingTimeout;

        const updateContent = (changeDescription: string): void => {
            const contentContract = {
                type: model.type,
                nodes: []
            };

            const childNodes = this.containerModelBinder.getChildContracts(model.widgets);
            contentContract.nodes.push(...childNodes);

            const onValueUpdate = bindingContext?.template?.[contentContract.type]?.onValueUpdate;

            if (onValueUpdate) {
                onValueUpdate(contentContract, changeDescription);
            }
        };

        const scheduleUpdate = (changeDescription: string): void => {
            clearTimeout(savingTimeout);
            savingTimeout = setTimeout(() => updateContent(changeDescription), 500);
        };

        const binding: IWidgetBinding<ContentModel, ContentViewModel> = {
            displayName: `${model.type} content`,
            layer: model.type === bindingContext.activeLayer
                ? model.type
                : bindingContext.layer,
            readonly: model.type === bindingContext.activeLayer,
            name: "content",
            model: model,
            draggable: true,
            handler: ContentHandlers,
            applyChanges: async () => {
                await this.modelToViewModel(model, viewModel, bindingContext);
                this.eventManager.dispatchEvent(Events.ContentUpdate);
            },
            onCreate: () => {
                if (model.type === bindingContext?.activeLayer) {
                    this.eventManager.addEventListener(Events.ContentUpdate, scheduleUpdate);
                    binding.flow = ComponentFlow.None;
                }
                else {
                    binding.flow = ComponentFlow.Block;
                }
            },
            onDispose: () => {
                if (model.type === bindingContext?.activeLayer) {
                    this.eventManager.removeEventListener(Events.ContentUpdate, scheduleUpdate);
                }
            }
        };

        viewModel["widgetBinding"] = binding;
    }

    public async modelToViewModel(model: ContentModel, viewModel?: ContentViewModel, bindingContext?: Bag<any>): Promise<ContentViewModel> {
        if (!viewModel) {
            viewModel = new ContentViewModel();
        }

        let childBindingContext: Bag<any> = {};

        if (bindingContext) {
            childBindingContext = <Bag<any>>Objects.clone(bindingContext);
            childBindingContext.readonly = model.type !== bindingContext?.layer;
            childBindingContext.template = bindingContext.template;
            childBindingContext.styleManager = bindingContext.styleManager;
            childBindingContext.layer = model.type; // setting same layer for all child components
            childBindingContext.activeLayer = bindingContext.activeLayer;
        }

        const promises = model.widgets.map(widgetModel => {
            const widgetViewModelBinder = this.viewModelBinderSelector.getViewModelBinderByModel(widgetModel);
            return widgetViewModelBinder.modelToViewModel(widgetModel, null, childBindingContext);
        });

        const viewModels = await Promise.all<WidgetViewModel>(promises);

        if (!viewModel["widgetBinding"]) {
            this.createBinding(model, viewModel, bindingContext);
        }

        viewModel.widgets(viewModels);

        if (viewModels.length === 0) {
            if (bindingContext.activeLayer !== model.type) {
                viewModel.widgets.push(new ContentPart(`${model.type} content`));
            }
            else {
                viewModel.widgets.push(new Placeholder(`${model.type} content`));
            }
        }

        return viewModel;
    }

    public canHandleModel(model: ContentModel): boolean {
        return model instanceof ContentModel;
    }

    public async contractToViewModel(contentContract: Contract, bindingContext: Bag<any>): Promise<ContentViewModel> {
        const layoutModel = await this.contentModelBinder.contractToModel(contentContract, bindingContext);
        const layoutViewModel = await this.modelToViewModel(layoutModel, null, bindingContext);

        return layoutViewModel;
    }
}