import { Bag, Contract } from "@paperbits/common";
import { IModelBinder } from "@paperbits/common/editing";
import { ILocaleService } from "@paperbits/common/localization";
import { INavigationService, NavigationItemContract, NavigationItemModel } from "@paperbits/common/navigation";
import { IPermalinkResolver } from "@paperbits/common/permalinks";
import { SecurityModelBinder } from "@paperbits/common/security";
import { BuiltInRoles } from "@paperbits/common/user";
import { AnchorUtils } from "../text/anchorUtils";
import { BlockContract } from "../text/contracts";
import { MenuContract } from "./menuContract";
import { MenuModel } from "./menuModel";


export class MenuModelBinder implements IModelBinder<MenuModel> {
    constructor(
        private readonly permalinkResolver: IPermalinkResolver,
        private readonly navigationService: INavigationService,
        private readonly securityModelBinder: SecurityModelBinder<any, any>,
        private readonly localeService: ILocaleService
    ) { }

    public canHandleContract(contract: Contract): boolean {
        return contract.type === "menu";
    }

    public canHandleModel(model: Object): boolean {
        return model instanceof MenuModel;
    }

    private async getLanguageNavigationMenu(bindingContext: Bag<any>, layout: string): Promise<NavigationItemModel> {
        const locales = await this.localeService.getLocales();
        const currentLocale = await this.localeService.getCurrentLocaleCode();
        const defaultLocale = await this.localeService.getDefaultLocaleCode();
        const requestedLocaleCode = bindingContext?.locale || currentLocale || defaultLocale;
        const requestedLocale = locales.find(x => x.code === requestedLocaleCode);
        const languageNavItems: NavigationItemModel[] = [];

        for (const locale of locales) {
            const targetUrl = bindingContext?.contentItemKey
                ? await this.permalinkResolver.getUrlByTargetKey(bindingContext.contentItemKey, locale.code)
                : "/";

            let isActive = false;

            if (locale.code === requestedLocale.code) {
                if (layout === "horizontal") {
                    continue; // excluding current locale from list in horizontal layout.
                }

                isActive = true;
            }

            const languageNavItem = new NavigationItemModel();
            languageNavItem.label = locale.displayName;
            languageNavItem.targetUrl = targetUrl;
            languageNavItem.isActive = isActive;

            languageNavItems.push(languageNavItem);
        }

        let topLevelChildren;

        switch (layout) {
            case "horizontal":
                topLevelChildren = [{
                    label: requestedLocale.displayName,
                    nodes: languageNavItems
                }];
                break;

            case "vertical":
                topLevelChildren = languageNavItems;
                break;

            default:
                throw new Error(`Unsupported menu widget layout: ${layout}`);
        }

        const topLevelNavItem = new NavigationItemModel();
        topLevelNavItem.label = "Languages";
        topLevelNavItem.nodes = topLevelChildren;

        return topLevelNavItem;
    }

    private async processNavigationItem(locale: string, contract: NavigationItemContract, permalink: string, minHeading: number, maxHeading: number, level: number = 0): Promise<NavigationItemModel> {
        const navitemModel = new NavigationItemModel();
        navitemModel.label = contract.label;

        if (contract.navigationItems) {
            const tasks = [];

            contract.navigationItems.forEach(child => {
                tasks.push(this.processNavigationItem(locale, child, permalink, minHeading, maxHeading, level + 1));
            });

            const results = await Promise.all(tasks);

            results.forEach(child => {
                navitemModel.nodes.push(child);
            });
        }

        if (!contract.targetKey) {
            return navitemModel;
        }

        const targetUrl = await this.permalinkResolver.getUrlByTargetKey(contract.targetKey, locale);
        navitemModel.targetUrl = targetUrl;
        navitemModel.targetWindow = contract.targetWindow;
        navitemModel.targetKey = contract.targetKey;
        navitemModel.anchor = contract.anchor;
        navitemModel.triggerEvent = contract.triggerEvent;

        if (targetUrl === permalink) {
            navitemModel.isActive = true;
        }

        if (level > 0 && minHeading && maxHeading && navitemModel.targetUrl === permalink) {
            const localNavItems = await this.processAnchorItems(permalink, locale, minHeading, maxHeading);
            navitemModel.nodes.push(...localNavItems);
        }

        return navitemModel;
    }

    private async processAnchorItems(permalink: string, locale: string, minHeading: number, maxHeading?: number): Promise<NavigationItemModel[]> {
        const content = await this.permalinkResolver.getContentByPermalink(permalink, locale);

        if (!content) {
            // throw new Error(`Unable to fetch content for permalink ${permalink}.`);
            return [];
        }

        const children = AnchorUtils.getHeadingNodes(content, minHeading, maxHeading);

        if (children.length === 0) {
            return [];
        }

        return children.filter(item => item.nodes && item.nodes.length > 0).map((item: BlockContract) => {
            const itemModel = new NavigationItemModel();
            itemModel.label = item.nodes[0].text;
            itemModel.targetUrl = `#${item.identifier || item.attrs?.id || item.attrs?.key}`;
            return itemModel;
        });
    }

    public async contractToModel(contract: MenuContract, bindingContext?: Bag<any>): Promise<MenuModel> {
        if (!contract) {
            throw new Error(`Parameter "contract" not specified.`);
        }

        const menuModel = new MenuModel();
        menuModel.minHeading = contract.minHeading;
        menuModel.maxHeading = contract.maxHeading;
        menuModel.items = [];
        menuModel.layout = contract.layout;
        menuModel.styles = contract.styles || { appearance: "components/menu/default" };

        if (contract.roles) { // converting legacy security contract
            contract.security = {
                roles: contract.roles
            }
        }

        if (contract.security) {
            menuModel.security = await this.securityModelBinder.contractToModel(contract.security);
        }

        const currentPageUrl = bindingContext?.navigationPath;

        if (contract.navigationItemKey) {
            let root: NavigationItemModel;

            if (contract.navigationItemKey === "@locales") {
                root = await this.getLanguageNavigationMenu(bindingContext, menuModel.layout);
            }
            else {
                const rootNavigationItem = await this.navigationService.getNavigationItem(contract.navigationItemKey);

                if (rootNavigationItem) {
                    root = await this.processNavigationItem(bindingContext?.locale, rootNavigationItem, currentPageUrl, menuModel.minHeading, menuModel.maxHeading);
                }
            }

            if (root) {
                menuModel.items = root.nodes;
                menuModel.navigationItem = root;
                menuModel.navigationItem.key = contract.navigationItemKey;
            }
        }

        if (!!currentPageUrl && menuModel.items.length === 0 && menuModel.minHeading && menuModel.maxHeading) {
            const localNavItems = await this.processAnchorItems(currentPageUrl, bindingContext?.locale, menuModel.minHeading, menuModel.maxHeading);
            menuModel.items.push(...localNavItems);
        }

        return menuModel;
    }

    public modelToContract(model: MenuModel): Contract {
        const contract: MenuContract = {
            type: "menu",
            minHeading: model.minHeading,
            maxHeading: model.maxHeading,
            navigationItemKey: model.navigationItem?.key,
            layout: model.layout,
            styles: model.styles
        };

        if (model.security) {
            contract.security = this.securityModelBinder.modelToContract(model.security);
        }


        return contract;
    }
}
